def _get_basic_certificate_text(self) -> List[str]: certificate = self.certificate_chain[0] public_key = self.certificate_chain[0].public_key() text_output = [ self._format_field('SHA1 Fingerprint:', binascii.hexlify(certificate.fingerprint(hashes.SHA1())).decode('ascii')), self._format_field('Common Name:', CertificateUtils.get_name_as_short_text(certificate.subject)), self._format_field('Issuer:', CertificateUtils.get_name_as_short_text(certificate.issuer)), self._format_field('Serial Number:', certificate.serial_number), self._format_field('Not Before:', certificate.not_valid_before), self._format_field('Not After:', certificate.not_valid_after), self._format_field('Signature Algorithm:', certificate.signature_hash_algorithm.name), self._format_field('Public Key Algorithm:', CertificateUtils.get_public_key_type(certificate))] if isinstance(public_key, EllipticCurvePublicKey): text_output.append(self._format_field('Key Size:', public_key.curve.key_size)) text_output.append(self._format_field('Curve:', public_key.curve.name)) elif isinstance(public_key, RSAPublicKey): text_output.append(self._format_field('Key Size:', public_key.key_size)) text_output.append(self._format_field('Exponent:', '{0} (0x{0:x})'.format(public_key.public_numbers().e))) else: # DSA Key? https://github.com/nabla-c0d3/sslyze/issues/314 pass try: # Print the SAN extension if there's one text_output.append(self._format_field('DNS Subject Alternative Names:', str(CertificateUtils.get_dns_subject_alternative_names(certificate)))) except KeyError: pass return text_output
def _get_basic_certificate_text(self) -> List[str]: certificate = self.certificate_chain[0] public_key = self.certificate_chain[0].public_key() text_output = [ self._format_field('SHA1 Fingerprint:', binascii.hexlify(certificate.fingerprint(hashes.SHA1())).decode('ascii')), self._format_field('Common Name:', CertificateUtils.get_name_as_short_text(certificate.subject)), self._format_field('Issuer:', CertificateUtils.get_name_as_short_text(certificate.issuer)), self._format_field('Serial Number:', certificate.serial_number), self._format_field('Not Before:', certificate.not_valid_before), self._format_field('Not After:', certificate.not_valid_after), self._format_field('Signature Algorithm:', certificate.signature_hash_algorithm.name), self._format_field('Public Key Algorithm:', CertificateUtils.get_public_key_type(certificate))] if isinstance(public_key, EllipticCurvePublicKey): text_output.append(self._format_field('Key Size:', public_key.curve.key_size)) text_output.append(self._format_field('Curve:', public_key.curve.name)) elif isinstance(public_key, RSAPublicKey): text_output.append(self._format_field('Key Size:', public_key.key_size)) text_output.append(self._format_field('Exponent:', '{0} (0x{0:x})'.format(public_key.public_numbers().e))) else: # DSA Key? https://github.com/nabla-c0d3/sslyze/issues/314 pass try: # Print the SAN extension if there's one text_output.append(self._format_field('DNS Subject Alternative Names:', str(CertificateUtils.get_dns_subject_alternative_names(certificate)))) except KeyError: pass return text_output
def _object_to_json_dict(obj): """Convert an object to a dictionary suitable for the JSON output. """ if isinstance(obj, Enum): # Properly serialize Enums (such as OpenSslVersionEnum) result = obj.name elif isinstance(obj, x509._Certificate): # Properly serialize certificates; only return the PEM string result = { 'as_pem': obj.public_bytes(Encoding.PEM).decode('ascii'), 'hpkp_pin': CertificateUtils.get_hpkp_pin(obj), 'subject_name': CertificateUtils.get_name_as_short_text(obj.subject) } elif isinstance(obj, object): result = {} for key, value in obj.__dict__.items(): # Remove private attributes if key.startswith('_'): continue result[key] = value else: raise TypeError('Unknown type: {}'.format(repr(obj))) return result
def as_text(self) -> List[str]: txt_result = [self._format_title(self.scan_command.get_title())] if self.hsts_header: txt_result.append(self._format_subtitle('HTTP Strict Transport Security (HSTS)')) txt_result.append(self._format_field("Max Age:", str(self.hsts_header.max_age))) txt_result.append(self._format_field("Include Subdomains:", str(self.hsts_header.include_subdomains))) txt_result.append(self._format_field("Preload:", str(self.hsts_header.preload))) else: txt_result.append(self._format_field("NOT SUPPORTED - Server did not send an HSTS header", "")) computed_hpkp_pins_text = ['', self._format_subtitle('Computed HPKP Pins for Current Chain')] if self.verified_certificate_chain: for index, cert in enumerate(self.verified_certificate_chain, start=0): final_subject = CertificateUtils.get_name_as_short_text(cert.subject) if len(final_subject) > 40: # Make the CN shorter when displaying it final_subject = '{}...'.format(final_subject[:40]) computed_hpkp_pins_text.append( self.PIN_TXT_FORMAT(('{} - {}'.format(index, final_subject)), CertificateUtils.get_hpkp_pin(cert)) ) else: computed_hpkp_pins_text.append( self._format_field('ERROR - Could not build verified chain (certificate untrusted?)', '') ) txt_result.extend(['', self._format_subtitle('HTTP Public Key Pinning (HPKP)')]) if self.hpkp_header: txt_result.append(self._format_field("Max Age:", str(self.hpkp_header.max_age))) txt_result.append(self._format_field("Include Subdomains:", str(self.hpkp_header.include_subdomains))) txt_result.append(self._format_field("Report URI:", str(self.hpkp_header.report_uri))) txt_result.append(self._format_field("Report Only:", str(self.hpkp_header.report_only))) txt_result.append(self._format_field("SHA-256 Pin List:", ', '.join(self.hpkp_header.pin_sha256_list))) if self.verified_certificate_chain: pin_validation_txt = 'OK - One of the configured pins was found in the certificate chain' \ if self.is_valid_pin_configured \ else 'FAILED - Could NOT find any of the configured pins in the certificate chain!' txt_result.append(self._format_field("Valid Pin:", pin_validation_txt)) backup_txt = 'OK - Backup pin found in the configured pins' \ if self.is_backup_pin_configured \ else 'FAILED - No backup pin found: all the configured pins are in the certificate chain!' txt_result.append(self._format_field("Backup Pin:", backup_txt)) else: txt_result.append(self._format_field("NOT SUPPORTED - Server did not send an HPKP header", "")) # Dislpay computed HPKP pins txt_result.extend(computed_hpkp_pins_text) txt_result.extend(['', self._format_subtitle('HTTP Expect-CT')]) if self.expect_ct_header: txt_result.append(self._format_field('Max Age:', str(self.expect_ct_header.max_age))) txt_result.append(self._format_field('Report- URI:', str(self.expect_ct_header.report_uri))) txt_result.append(self._format_field('Enforce:', str(self.expect_ct_header.enforce))) else: txt_result.append(self._format_field("NOT SUPPORTED - Server did not send an Expect-CT header", "")) return txt_result
def as_text(self): txt_result = [self._format_title(self.scan_command.get_title())] if self.hsts_header: txt_result.append(self._format_subtitle('HTTP Strict Transport Security (HSTS)')) txt_result.append(self._format_field("Max Age:", str(self.hsts_header.max_age))) txt_result.append(self._format_field("Include Subdomains:", str(self.hsts_header.include_subdomains))) txt_result.append(self._format_field("Preload:", str(self.hsts_header.preload))) else: txt_result.append(self._format_field("NOT SUPPORTED - Server did not send an HSTS header", "")) computed_hpkp_pins_text = ['', self._format_subtitle('Computed HPKP Pins for Current Chain')] if self.verified_certificate_chain: for index, cert in enumerate(self.verified_certificate_chain, start=0): final_subject = CertificateUtils.get_name_as_short_text(cert.subject) if len(final_subject) > 40: # Make the CN shorter when displaying it final_subject = '{}...'.format(final_subject[:40]) computed_hpkp_pins_text.append( self.PIN_TXT_FORMAT(('{} - {}'.format(index, final_subject)), CertificateUtils.get_hpkp_pin(cert)) ) else: computed_hpkp_pins_text.append( self._format_field('ERROR - Could not build verified chain (certificate untrusted?)', '') ) txt_result.extend(['', self._format_subtitle('HTTP Public Key Pinning (HPKP)')]) if self.hpkp_header: txt_result.append(self._format_field("Max Age:", str(self.hpkp_header.max_age))) txt_result.append(self._format_field("Include Subdomains:", str(self.hpkp_header.include_subdomains))) txt_result.append(self._format_field("Report URI:", self.hpkp_header.report_uri)) txt_result.append(self._format_field("Report Only:", str(self.hpkp_header.report_only))) txt_result.append(self._format_field("SHA-256 Pin List:", ', '.join(self.hpkp_header.pin_sha256_list))) if self.verified_certificate_chain: pin_validation_txt = 'OK - One of the configured pins was found in the certificate chain' \ if self.is_valid_pin_configured \ else 'FAILED - Could NOT find any of the configured pins in the certificate chain!' txt_result.append(self._format_field("Valid Pin:", pin_validation_txt)) backup_txt = 'OK - Backup pin found in the configured pins' \ if self.is_backup_pin_configured \ else 'FAILED - No backup pin found: all the configured pins are in the certificate chain!' txt_result.append(self._format_field("Backup Pin:", backup_txt)) else: txt_result.append(self._format_field("NOT SUPPORTED - Server did not send an HPKP header", "")) # Dislpay computed HPKP pins txt_result.extend(computed_hpkp_pins_text) txt_result.extend(['', self._format_subtitle('HTTP Expect-CT')]) if self.expect_ct_header: txt_result.append(self._format_field('Max Age:', str(self.expect_ct_header.max_age))) txt_result.append(self._format_field('Report- URI:', self.expect_ct_header.report_uri)) txt_result.append(self._format_field('Enforce:', str(self.expect_ct_header.enforce))) else: txt_result.append(self._format_field("NOT SUPPORTED - Server did not send an Expect-CT header", "")) return txt_result
def test(self): leaf_path = os.path.join(os.path.dirname(__file__), '..', 'utils', 'github.com.pem') with open(leaf_path, 'rb') as leaf_file: leaf_pem = leaf_file.read() certificate = load_pem_x509_certificate(leaf_pem, default_backend()) self.assertIsNone( CertificateUtils.matches_hostname(certificate, 'www.github.com')) with self.assertRaises(ssl.CertificateError): self.assertFalse( CertificateUtils.matches_hostname(certificate, 'notgithub.com')) self.assertEqual( CertificateUtils.get_common_names(certificate.subject), ['github.com']) self.assertEqual( CertificateUtils.get_dns_subject_alternative_names(certificate), ['github.com', 'www.github.com']) self.assertEqual( CertificateUtils.get_name_as_short_text(certificate.issuer), 'DigiCert SHA2 Extended Validation Server CA')
def test(self): leaf_path = os.path.join(os.path.dirname(__file__), '..', 'utils', 'github.com.pem') with open(leaf_path, 'rb') as leaf_file: leaf_pem = leaf_file.read() certificate = load_pem_x509_certificate(leaf_pem, default_backend()) self.assertIsNone(CertificateUtils.matches_hostname(certificate, 'www.github.com')) with self.assertRaises(ssl.CertificateError): self.assertFalse(CertificateUtils.matches_hostname(certificate, 'notgithub.com')) self.assertEqual(CertificateUtils.get_common_names(certificate.subject), ['github.com']) self.assertEqual(CertificateUtils.get_dns_subject_alternative_names(certificate), ['github.com', 'www.github.com']) self.assertEqual(CertificateUtils.get_name_as_short_text(certificate.issuer), 'DigiCert SHA2 Extended Validation Server CA')
def test(self): leaf_path = Path(__file__).absolute().parent / '..' / 'utils' / 'github.com.pem' leaf_pem = leaf_path.read_bytes() certificate = load_pem_x509_certificate(leaf_pem, default_backend()) assert CertificateUtils.matches_hostname(certificate, 'www.github.com') is None with pytest.raises(ssl.CertificateError): assert not CertificateUtils.matches_hostname(certificate, 'notgithub.com') assert CertificateUtils.get_common_names(certificate.subject) == ['github.com'] assert CertificateUtils.get_dns_subject_alternative_names(certificate) == [ 'github.com', 'www.github.com' ] expected_name = 'DigiCert SHA2 Extended Validation Server CA' assert CertificateUtils.get_name_as_short_text(certificate.issuer) == expected_name
def test(self): leaf_path = Path( __file__).absolute().parent / '..' / 'utils' / 'github.com.pem' leaf_pem = leaf_path.read_bytes() certificate = load_pem_x509_certificate(leaf_pem, default_backend()) assert CertificateUtils.matches_hostname(certificate, 'www.github.com') is None with pytest.raises(ssl.CertificateError): assert not CertificateUtils.matches_hostname( certificate, 'notgithub.com') assert CertificateUtils.get_common_names( certificate.subject) == ['github.com'] assert CertificateUtils.get_dns_subject_alternative_names( certificate) == ['github.com', 'www.github.com'] expected_name = 'DigiCert SHA2 Extended Validation Server CA' assert CertificateUtils.get_name_as_short_text( certificate.issuer) == expected_name
def as_text(self) -> List[str]: text_output = [self._format_title(self.scan_command.get_title()), self._format_subtitle('Content')] text_output.extend(self._get_basic_certificate_text()) # Trust section text_output.extend(['', self._format_subtitle('Trust')]) # Hostname validation server_name_indication = self.server_info.tls_server_name_indication if self.server_info.tls_server_name_indication != self.server_info.hostname: text_output.append(self._format_field("SNI enabled with virtual domain:", server_name_indication)) hostname_validation_text = 'OK - Certificate matches {hostname}'.format(hostname=server_name_indication) \ if self.certificate_matches_hostname \ else 'FAILED - Certificate does NOT match {hostname}'.format(hostname=server_name_indication) text_output.append(self._format_field('Hostname Validation:', hostname_validation_text)) # Path validation that was successfully tested for path_result in self.path_validation_result_list: if path_result.is_certificate_trusted: # EV certs - Only Mozilla supported for now ev_txt = '' if self.is_leaf_certificate_ev and path_result.trust_store.ev_oids: ev_txt = ', Extended Validation' path_txt = 'OK - Certificate is trusted{}'.format(ev_txt) else: path_txt = 'FAILED - Certificate is NOT Trusted: {}'.format(path_result.verify_string) text_output.append(self._format_field( self.TRUST_FORMAT.format(store_name=path_result.trust_store.name, store_version=path_result.trust_store.version), path_txt)) # Path validation that ran into errors for path_error in self.path_validation_error_list: error_txt = 'ERROR: {}'.format(path_error.error_message) text_output.append(self._format_field( self.TRUST_FORMAT.format( store_name=path_error.trust_store.name, store_version=path_error.trust_store.version ), error_txt)) if self.symantec_distrust_timeline is not None: timeline_str = 'March 2018' if self.symantec_distrust_timeline == SymantecDistrustTimelineEnum.MARCH_2018 \ else 'September 2018' symantec_str = 'WARNING: Certificate distrusted by Google and Mozilla on {}'.format(timeline_str) else: symantec_str = 'OK - Not a Symantec-issued certificate' text_output.append(self._format_field('Symantec 2018 Deprecation:', symantec_str)) # Print the Common Names within the certificate chain cns_in_certificate_chain = [CertificateUtils.get_name_as_short_text(cert.subject) for cert in self.certificate_chain] text_output.append(self._format_field('Received Chain:', ' --> '.join(cns_in_certificate_chain))) # Print the Common Names within the verified certificate chain if validation was successful if self.verified_certificate_chain: cns_in_certificate_chain = [CertificateUtils.get_name_as_short_text(cert.subject) for cert in self.verified_certificate_chain] verified_chain_txt = ' --> '.join(cns_in_certificate_chain) else: verified_chain_txt = self.NO_VERIFIED_CHAIN_ERROR_TXT text_output.append(self._format_field('Verified Chain:', verified_chain_txt)) if self.verified_certificate_chain: chain_with_anchor_txt = 'OK - Anchor certificate not sent' if not self.has_anchor_in_certificate_chain \ else 'WARNING - Received certificate chain contains the anchor certificate' else: chain_with_anchor_txt = self.NO_VERIFIED_CHAIN_ERROR_TXT text_output.append(self._format_field('Received Chain Contains Anchor:', chain_with_anchor_txt)) chain_order_txt = 'OK - Order is valid' if self.is_certificate_chain_order_valid \ else 'FAILED - Certificate chain out of order!' text_output.append(self._format_field('Received Chain Order:', chain_order_txt)) if self.verified_certificate_chain: sha1_text = 'OK - No SHA1-signed certificate in the verified certificate chain' \ if not self.has_sha1_in_certificate_chain \ else 'INSECURE - SHA1-signed certificate in the verified certificate chain' else: sha1_text = self.NO_VERIFIED_CHAIN_ERROR_TXT text_output.append(self._format_field('Verified Chain contains SHA1:', sha1_text)) # Extensions section text_output.extend(['', self._format_subtitle('Extensions')]) # OCSP must-staple must_staple_txt = 'OK - Extension present' \ if self.certificate_has_must_staple_extension \ else 'NOT SUPPORTED - Extension not found' text_output.append(self._format_field('OCSP Must-Staple:', must_staple_txt)) # Look for SCT extension scts_count = self.certificate_included_scts_count if scts_count is None: sct_txt = 'OK - Extension present' elif scts_count == 0: sct_txt = 'NOT SUPPORTED - Extension not found' elif scts_count < 3: sct_txt = 'WARNING - Only {} SCTs included but Google recommends 3 or more'.format(str(scts_count)) else: sct_txt = 'OK - {} SCTs included'.format(str(scts_count)) text_output.append(self._format_field('Certificate Transparency:', sct_txt)) # OCSP stapling text_output.extend(['', self._format_subtitle('OCSP Stapling')]) if self.ocsp_response is None: text_output.append(self._format_field('', 'NOT SUPPORTED - Server did not send back an OCSP response')) else: if self.ocsp_response_status != OcspResponseStatusEnum.SUCCESSFUL: ocsp_resp_txt = [self._format_field('', 'ERROR - OCSP response status is not successful: {}'.format( self.ocsp_response_status.name # type: ignore ))] else: ocsp_trust_txt = 'OK - Response is trusted' \ if self.is_ocsp_response_trusted \ else 'FAILED - Response is NOT trusted' ocsp_resp_txt = [ self._format_field('OCSP Response Status:', self.ocsp_response['responseStatus']), self._format_field('Validation w/ Mozilla Store:', ocsp_trust_txt), self._format_field('Responder Id:', self.ocsp_response['responderID'])] if 'successful' in self.ocsp_response['responseStatus']: ocsp_resp_txt.extend([ self._format_field('Cert Status:', self.ocsp_response['responses'][0]['certStatus']), self._format_field('Cert Serial Number:', self.ocsp_response['responses'][0]['certID']['serialNumber']), self._format_field('This Update:', self.ocsp_response['responses'][0]['thisUpdate']), self._format_field('Next Update:', self.ocsp_response['responses'][0]['nextUpdate']) ]) text_output.extend(ocsp_resp_txt) # All done return text_output
def as_text(self) -> List[str]: text_output = [self._format_title(self.scan_command.get_title()), self._format_subtitle('Content')] text_output.extend(self._get_basic_certificate_text()) # Trust section text_output.extend(['', self._format_subtitle('Trust')]) # Hostname validation server_name_indication = self.server_info.tls_server_name_indication if self.server_info.tls_server_name_indication != self.server_info.hostname: text_output.append(self._format_field("SNI enabled with virtual domain:", server_name_indication)) hostname_validation_text = 'OK - Certificate matches {hostname}'.format(hostname=server_name_indication) \ if self.certificate_matches_hostname \ else 'FAILED - Certificate does NOT match {hostname}'.format(hostname=server_name_indication) text_output.append(self._format_field('Hostname Validation:', hostname_validation_text)) # Path validation that was successfully tested for path_result in self.path_validation_result_list: if path_result.is_certificate_trusted: # EV certs - Only Mozilla supported for now ev_txt = '' if self.is_leaf_certificate_ev and path_result.trust_store.ev_oids: ev_txt = ', Extended Validation' path_txt = 'OK - Certificate is trusted{}'.format(ev_txt) else: path_txt = 'FAILED - Certificate is NOT Trusted: {}'.format(path_result.verify_string) text_output.append(self._format_field( self.TRUST_FORMAT.format(store_name=path_result.trust_store.name, store_version=path_result.trust_store.version), path_txt)) # Path validation that ran into errors for path_error in self.path_validation_error_list: error_txt = 'ERROR: {}'.format(path_error.error_message) text_output.append(self._format_field( self.TRUST_FORMAT.format( store_name=path_error.trust_store.name, store_version=path_error.trust_store.version ), error_txt)) if self.symantec_distrust_timeline is not None: timeline_str = 'March 2018' if self.symantec_distrust_timeline == SymantecDistrustTimelineEnum.MARCH_2018 \ else 'September 2018' symantec_str = 'WARNING: Certificate distrusted by Google and Mozilla on {}'.format(timeline_str) else: symantec_str = 'OK - Not a Symantec-issued certificate' text_output.append(self._format_field('Symantec 2018 Deprecation:', symantec_str)) # Print the Common Names within the certificate chain cns_in_certificate_chain = [CertificateUtils.get_name_as_short_text(cert.subject) for cert in self.certificate_chain] text_output.append(self._format_field('Received Chain:', ' --> '.join(cns_in_certificate_chain))) # Print the Common Names within the verified certificate chain if validation was successful if self.verified_certificate_chain: cns_in_certificate_chain = [CertificateUtils.get_name_as_short_text(cert.subject) for cert in self.verified_certificate_chain] verified_chain_txt = ' --> '.join(cns_in_certificate_chain) else: verified_chain_txt = self.NO_VERIFIED_CHAIN_ERROR_TXT text_output.append(self._format_field('Verified Chain:', verified_chain_txt)) if self.verified_certificate_chain: chain_with_anchor_txt = 'OK - Anchor certificate not sent' if not self.has_anchor_in_certificate_chain \ else 'WARNING - Received certificate chain contains the anchor certificate' else: chain_with_anchor_txt = self.NO_VERIFIED_CHAIN_ERROR_TXT text_output.append(self._format_field('Received Chain Contains Anchor:', chain_with_anchor_txt)) chain_order_txt = 'OK - Order is valid' if self.is_certificate_chain_order_valid \ else 'FAILED - Certificate chain out of order!' text_output.append(self._format_field('Received Chain Order:', chain_order_txt)) if self.verified_certificate_chain: sha1_text = 'OK - No SHA1-signed certificate in the verified certificate chain' \ if not self.has_sha1_in_certificate_chain \ else 'INSECURE - SHA1-signed certificate in the verified certificate chain' else: sha1_text = self.NO_VERIFIED_CHAIN_ERROR_TXT text_output.append(self._format_field('Verified Chain contains SHA1:', sha1_text)) # Extensions section text_output.extend(['', self._format_subtitle('Extensions')]) # OCSP must-staple must_staple_txt = 'OK - Extension present' \ if self.certificate_has_must_staple_extension \ else 'NOT SUPPORTED - Extension not found' text_output.append(self._format_field('OCSP Must-Staple:', must_staple_txt)) # Look for SCT extension scts_count = self.certificate_included_scts_count if scts_count is None: sct_txt = 'OK - Extension present' elif scts_count == 0: sct_txt = 'NOT SUPPORTED - Extension not found' elif scts_count < 3: sct_txt = 'WARNING - Only {} SCTs included but Google recommends 3 or more'.format(str(scts_count)) else: sct_txt = 'OK - {} SCTs included'.format(str(scts_count)) text_output.append(self._format_field('Certificate Transparency:', sct_txt)) # OCSP stapling text_output.extend(['', self._format_subtitle('OCSP Stapling')]) if self.ocsp_response is None: text_output.append(self._format_field('', 'NOT SUPPORTED - Server did not send back an OCSP response')) else: if self.ocsp_response_status != OcspResponseStatusEnum.SUCCESSFUL: ocsp_resp_txt = [self._format_field('', 'ERROR - OCSP response status is not successful: {}'.format( self.ocsp_response_status.name # type: ignore ))] else: ocsp_trust_txt = 'OK - Response is trusted' \ if self.is_ocsp_response_trusted \ else 'FAILED - Response is NOT trusted' ocsp_resp_txt = [ self._format_field('OCSP Response Status:', self.ocsp_response['responseStatus']), self._format_field('Validation w/ Mozilla Store:', ocsp_trust_txt), self._format_field('Responder Id:', self.ocsp_response['responderID'])] if 'successful' in self.ocsp_response['responseStatus']: ocsp_resp_txt.extend([ self._format_field('Cert Status:', self.ocsp_response['responses'][0]['certStatus']), self._format_field('Cert Serial Number:', self.ocsp_response['responses'][0]['certID']['serialNumber']), self._format_field('This Update:', self.ocsp_response['responses'][0]['thisUpdate']), self._format_field('Next Update:', self.ocsp_response['responses'][0]['nextUpdate']) ]) text_output.extend(ocsp_resp_txt) # All done return text_output
# Each scan result has attributes with the information yo're looking for, specific to each scan command # All these attributes are documented within each scan command's module if isinstance(scan_result.scan_command, Tlsv12ScanCommand): # Do something with the result print('TLS 1.2 cipher suites') for cipher in scan_result.accepted_cipher_list: print(' {}'.format(cipher.name)) elif isinstance(scan_result.scan_command, SessionRenegotiationScanCommand): reneg_result = scan_result print('Client renegotiation: {}'.format(scan_result.accepts_client_renegotiation)) print('Secure renegotiation: {}'.format(scan_result.supports_secure_renegotiation)) elif isinstance(scan_result.scan_command, CertificateInfoScanCommand): # Print the Common Names within the certificate chain cns_in_certificate_chain = [CertificateUtils.get_name_as_short_text(cert.subject) for cert in scan_result.verified_certificate_chain] print('Certificate Chain CNn: {}'.format(cns_in_certificate_chain)) # All the scan command results also always expose two APIs # What the SSLyze CLI would output to the console print('\nSSLyze text output') for line in reneg_result.as_text(): print(line) print('\nSSLyze XML node') # The XML node for the SSLyze CLI XML output print(reneg_result.as_xml())
# Each scan result has attributes with the information yo're looking for, specific to each scan command # All these attributes are documented within each scan command's module if isinstance(scan_result.scan_command, Tlsv12ScanCommand): # Do something with the result print('TLS 1.2 cipher suites') for cipher in scan_result.accepted_cipher_list: print(' {}'.format(cipher.name)) elif isinstance(scan_result.scan_command, SessionRenegotiationScanCommand): reneg_result = scan_result print('Client renegotiation: {}'.format(scan_result.accepts_client_renegotiation)) print('Secure renegotiation: {}'.format(scan_result.supports_secure_renegotiation)) elif isinstance(scan_result.scan_command, CertificateInfoScanCommand): # Print the Common Names within the certificate chain cns_in_certificate_chain = [CertificateUtils.get_name_as_short_text(cert.subject) for cert in scan_result.verified_certificate_chain] print('Certificate Chain CNn: {}'.format(cns_in_certificate_chain)) # All the scan command results also always expose two APIs # What the SSLyze CLI would output to the console print('\nSSLyze text output') for line in reneg_result.as_text(): print(line) print('\nSSLyze XML node') # The XML node for the SSLyze CLI XML output print(reneg_result.as_xml())
def as_text(self) -> List[str]: txt_result = [self._format_title(self.scan_command.get_title()), ""] txt_result.append( self._format_subtitle( "Computed HPKP Pins for Server Certificate Chain")) if self.verified_certificate_chain: for index, cert in enumerate(self.verified_certificate_chain, start=0): final_subject = CertificateUtils.get_name_as_short_text( cert.subject) if len(final_subject) > 40: # Make the CN shorter when displaying it final_subject = "{}...".format(final_subject[:40]) txt_result.append( self._PIN_TXT_FORMAT( ("{} - {}".format(index, final_subject)), CertificateUtils.get_hpkp_pin(cert))) txt_result.append("") else: txt_result.append( self._format_field( "ERROR - Could not build verified chain (certificate untrusted?)", "")) txt_result.append("") txt_result.append( self._format_subtitle("Strict-Transport-Security Header")) if self.strict_transport_security_header: txt_result.append( self._format_field( "Max Age:", str(self.strict_transport_security_header.max_age))) txt_result.append( self._format_field( "Include Subdomains:", str(self.strict_transport_security_header. include_subdomains))) txt_result.append( self._format_field( "Preload:", str(self.strict_transport_security_header.preload))) else: txt_result.append(self._format_field(self._HEADER_NOT_SENT_TXT, "")) for header, subtitle in [ (self.public_key_pins_header, "Public-Key-Pins Header"), (self.public_key_pins_report_only_header, "Public-Key-Pins-Report-Only Header"), ]: txt_result.extend(["", self._format_subtitle(subtitle)]) if header: txt_result.append( self._format_field("Max Age:", str(header.max_age))) txt_result.append( self._format_field("Include Subdomains:", str(header.include_subdomains))) txt_result.append( self._format_field("Report URI:", str(header.report_uri))) txt_result.append( self._format_field("SHA-256 Pin List:", ", ".join(header.pin_sha256_list))) if self.verified_certificate_chain: pin_validation_txt = ( "OK - One of the configured pins was found in the certificate chain" if self.is_valid_pin_configured else "FAILED - Could NOT find any of the configured pins in the certificate chain!" ) txt_result.append( self._format_field("Valid Pin:", pin_validation_txt)) backup_txt = ( "OK - Backup pin found in the configured pins" if self.is_backup_pin_configured else "FAILED - No backup pin found: all the configured pins are in the certificate chain!" ) txt_result.append( self._format_field("Backup Pin:", backup_txt)) else: txt_result.append( self._format_field(self._HEADER_NOT_SENT_TXT, "")) txt_result.extend(["", self._format_subtitle("Expect-CT Header")]) if self.expect_ct_header: txt_result.append( self._format_field("Max Age:", str(self.expect_ct_header.max_age))) txt_result.append( self._format_field("Report- URI:", str(self.expect_ct_header.report_uri))) txt_result.append( self._format_field("Enforce:", str(self.expect_ct_header.enforce))) else: txt_result.append(self._format_field(self._HEADER_NOT_SENT_TXT, "")) return txt_result
def as_text(self): text_output = [self._format_title(self.scan_command.get_title())] text_output.append(self._format_subtitle('Content')) text_output.extend(self._get_basic_certificate_text()) # Trust section text_output.extend(['', self._format_subtitle('Trust')]) # Hostname validation server_name_indication = self.server_info.tls_server_name_indication if self.server_info.tls_server_name_indication != self.server_info.hostname: text_output.append( self._format_field("SNI enabled with virtual domain:", server_name_indication)) hostname_validation_text = 'OK - Certificate matches {hostname}'.format(hostname=server_name_indication) \ if self.certificate_matches_hostname \ else 'FAILED - Certificate does NOT match {hostname}'.format(hostname=server_name_indication) text_output.append( self._format_field('Hostname Validation:', hostname_validation_text)) # Path validation that was successfully tested for path_result in self.path_validation_result_list: if path_result.is_certificate_trusted: # EV certs - Only Mozilla supported for now ev_txt = '' if self.is_leaf_certificate_ev and TrustStoresRepository.get_main( ) == path_result.trust_store: ev_txt = ', Extended Validation' path_txt = 'OK - Certificate is trusted{}'.format(ev_txt) else: path_txt = 'FAILED - Certificate is NOT Trusted: {}'.format( path_result.verify_string) text_output.append( self._format_field( self.TRUST_FORMAT.format( store_name=path_result.trust_store.name, store_version=path_result.trust_store.version), path_txt)) # Path validation that ran into errors for path_error in self.path_validation_error_list: error_txt = 'ERROR: {}'.format(path_error.error_message) text_output.append( self._format_field( self.TRUST_FORMAT.format( store_name=path_result.trust_store.name, store_version=path_result.trust_store.version), error_txt)) # Print the Common Names within the certificate chain cns_in_certificate_chain = [ CertificateUtils.get_name_as_short_text(cert.subject) for cert in self.certificate_chain ] text_output.append( self._format_field('Received Chain:', ' --> '.join(cns_in_certificate_chain))) # Print the Common Names within the verified certificate chain if validation was successful if self.verified_certificate_chain: cns_in_certificate_chain = [ CertificateUtils.get_name_as_short_text(cert.subject) for cert in self.verified_certificate_chain ] verified_chain_txt = ' --> '.join(cns_in_certificate_chain) else: verified_chain_txt = self.NO_VERIFIED_CHAIN_ERROR_TXT text_output.append( self._format_field('Verified Chain:', verified_chain_txt)) if self.verified_certificate_chain: chain_with_anchor_txt = 'OK - Anchor certificate not sent' if not self.has_anchor_in_certificate_chain \ else 'WARNING - Received certificate chain contains the anchor certificate' else: chain_with_anchor_txt = self.NO_VERIFIED_CHAIN_ERROR_TXT text_output.append( self._format_field('Received Chain Contains Anchor:', chain_with_anchor_txt)) chain_order_txt = 'OK - Order is valid' if self.is_certificate_chain_order_valid \ else 'FAILED - Certificate chain out of order!' text_output.append( self._format_field('Received Chain Order:', chain_order_txt)) if self.verified_certificate_chain: sha1_text = 'OK - No SHA1-signed certificate in the verified certificate chain' \ if not self.has_sha1_in_certificate_chain \ else 'INSECURE - SHA1-signed certificate in the verified certificate chain' else: sha1_text = self.NO_VERIFIED_CHAIN_ERROR_TXT text_output.append( self._format_field('Verified Chain contains SHA1:', sha1_text)) # OCSP stapling text_output.extend(['', self._format_subtitle('OCSP Stapling')]) if self.ocsp_response is None: text_output.append( self._format_field( '', 'NOT SUPPORTED - Server did not send back an OCSP response.' )) else: ocsp_trust_txt = 'OK - Response is trusted' \ if self.is_ocsp_response_trusted \ else 'FAILED - Response is NOT trusted' ocsp_resp_txt = [ self._format_field('OCSP Response Status:', self.ocsp_response['responseStatus']), self._format_field('Validation w/ Mozilla Store:', ocsp_trust_txt), self._format_field('Responder Id:', self.ocsp_response['responderID']) ] if 'successful' in self.ocsp_response['responseStatus']: ocsp_resp_txt.extend([ self._format_field( 'Cert Status:', self.ocsp_response['responses'][0]['certStatus']), self._format_field( 'Cert Serial Number:', self.ocsp_response['responses'] [0]['certID']['serialNumber']), self._format_field( 'This Update:', self.ocsp_response['responses'][0]['thisUpdate']), self._format_field( 'Next Update:', self.ocsp_response['responses'][0]['nextUpdate']) ]) text_output.extend(ocsp_resp_txt) # All done return text_output
if isinstance(scan_result.scan_command, Tlsv12ScanCommand): # Do something with the result print('TLS 1.2 cipher suites') for cipher in scan_result.accepted_cipher_list: print(' {}'.format(cipher.name)) elif isinstance(scan_result.scan_command, SessionRenegotiationScanCommand): reneg_result = scan_result print('Client renegotiation: {}'.format( scan_result.accepts_client_renegotiation)) print('Secure renegotiation: {}'.format( scan_result.supports_secure_renegotiation)) elif isinstance(scan_result.scan_command, CertificateInfoScanCommand): # Print the Common Names within the certificate chain cns_in_certificate_chain = [ CertificateUtils.get_name_as_short_text(cert.subject) for cert in scan_result.verified_certificate_chain ] print('Certificate Chain CNn: {}'.format(cns_in_certificate_chain)) # All the scan command results also always expose two APIs # What the SSLyze CLI would output to the console print('\nSSLyze text output') for line in reneg_result.as_text(): print(line) print('\nSSLyze XML node') # The XML node for the SSLyze CLI XML output print(reneg_result.as_xml())
def as_text(self) -> List[str]: txt_result = [self._format_title(self.scan_command.get_title()), ''] txt_result.append(self._format_subtitle('Computed HPKP Pins for Server Certificate Chain')) if self.verified_certificate_chain: for index, cert in enumerate(self.verified_certificate_chain, start=0): final_subject = CertificateUtils.get_name_as_short_text(cert.subject) if len(final_subject) > 40: # Make the CN shorter when displaying it final_subject = '{}...'.format(final_subject[:40]) txt_result.append( self._PIN_TXT_FORMAT(('{} - {}'.format(index, final_subject)), CertificateUtils.get_hpkp_pin(cert)) ) txt_result.append('') else: txt_result.append( self._format_field('ERROR - Could not build verified chain (certificate untrusted?)', '') ) txt_result.append('') txt_result.append(self._format_subtitle('Strict-Transport-Security Header')) if self.strict_transport_security_header: txt_result.append(self._format_field("Max Age:", str(self.strict_transport_security_header.max_age))) txt_result.append(self._format_field( "Include Subdomains:", str(self.strict_transport_security_header.include_subdomains)) ) txt_result.append(self._format_field("Preload:", str(self.strict_transport_security_header.preload))) else: txt_result.append(self._format_field(self._HEADER_NOT_SENT_TXT, "")) for header, subtitle in [ (self.public_key_pins_header, 'Public-Key-Pins Header'), (self.public_key_pins_report_only_header, 'Public-Key-Pins-Report-Only Header') ]: txt_result.extend(['', self._format_subtitle(subtitle)]) if header: txt_result.append(self._format_field("Max Age:", str(header.max_age))) txt_result.append(self._format_field("Include Subdomains:", str(header.include_subdomains))) txt_result.append(self._format_field("Report URI:", str(header.report_uri))) txt_result.append(self._format_field("SHA-256 Pin List:", ', '.join(header.pin_sha256_list))) if self.verified_certificate_chain: pin_validation_txt = 'OK - One of the configured pins was found in the certificate chain' \ if self.is_valid_pin_configured \ else 'FAILED - Could NOT find any of the configured pins in the certificate chain!' txt_result.append(self._format_field("Valid Pin:", pin_validation_txt)) backup_txt = 'OK - Backup pin found in the configured pins' \ if self.is_backup_pin_configured \ else 'FAILED - No backup pin found: all the configured pins are in the certificate chain!' txt_result.append(self._format_field("Backup Pin:", backup_txt)) else: txt_result.append(self._format_field(self._HEADER_NOT_SENT_TXT, "")) txt_result.extend(['', self._format_subtitle('Expect-CT Header')]) if self.expect_ct_header: txt_result.append(self._format_field('Max Age:', str(self.expect_ct_header.max_age))) txt_result.append(self._format_field('Report- URI:', str(self.expect_ct_header.report_uri))) txt_result.append(self._format_field('Enforce:', str(self.expect_ct_header.enforce))) else: txt_result.append(self._format_field(self._HEADER_NOT_SENT_TXT, "")) return txt_result