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 certificate = obj result = { # Add general info 'as_pem': obj.public_bytes(Encoding.PEM).decode('ascii'), 'hpkp_pin': CertificateUtils.get_hpkp_pin(obj), # Add some of the fields of the cert 'subject': CertificateUtils.get_name_as_text(certificate.subject), 'issuer': CertificateUtils.get_name_as_text(certificate.issuer), 'serialNumber': str(certificate.serial_number), 'notBefore': certificate.not_valid_before.strftime("%Y-%m-%d %H:%M:%S"), 'notAfter': certificate.not_valid_after.strftime("%Y-%m-%d %H:%M:%S"), 'signatureAlgorithm': certificate.signature_hash_algorithm.name, 'publicKey': { 'algorithm': CertificateUtils.get_public_key_type(certificate) }, } dns_alt_names = CertificateUtils.get_dns_subject_alternative_names( certificate) if dns_alt_names: result['subjectAlternativeName'] = {'DNS': dns_alt_names} # Add some info about the public key public_key = certificate.public_key() if isinstance(public_key, EllipticCurvePublicKey): result['publicKey']['size'] = str(public_key.curve.key_size) result['publicKey']['curve'] = public_key.curve.name else: result['publicKey']['size'] = str(public_key.key_size) result['publicKey']['exponent'] = str( public_key.public_numbers().e) elif isinstance(obj, object): if hasattr(obj, '__dict__'): result = {} for key, value in obj.__dict__.items(): # Remove private attributes if key.startswith('_'): continue result[key] = _object_to_json_dict(value) else: # Simple object like a string result = obj else: raise TypeError('Unknown type: {}'.format(repr(obj))) return result
def _certificate_chain_to_xml(certificate_chain: List[Certificate]) -> List[Element]: cert_xml_list = [] for certificate in certificate_chain: cert_xml = Element('certificate', attrib={ 'sha1Fingerprint': binascii.hexlify(certificate.fingerprint(hashes.SHA1())).decode('ascii'), 'hpkpSha256Pin': CertificateUtils.get_hpkp_pin(certificate) }) # Add the PEM cert cert_as_pem_xml = Element('asPEM') cert_as_pem_xml.text = certificate.public_bytes(Encoding.PEM).decode('ascii') cert_xml.append(cert_as_pem_xml) # Add some of the fields of the cert elem_xml = Element('subject') elem_xml.text = CertificateUtils.get_name_as_text(certificate.subject) cert_xml.append(elem_xml) elem_xml = Element('issuer') elem_xml.text = CertificateUtils.get_name_as_text(certificate.issuer) cert_xml.append(elem_xml) elem_xml = Element('serialNumber') elem_xml.text = str(certificate.serial_number) cert_xml.append(elem_xml) elem_xml = Element('notBefore') elem_xml.text = certificate.not_valid_before.strftime("%Y-%m-%d %H:%M:%S") cert_xml.append(elem_xml) elem_xml = Element('notAfter') elem_xml.text = certificate.not_valid_after.strftime("%Y-%m-%d %H:%M:%S") cert_xml.append(elem_xml) elem_xml = Element('signatureAlgorithm') elem_xml.text = certificate.signature_hash_algorithm.name cert_xml.append(elem_xml) key_attrs = {'algorithm': CertificateUtils.get_public_key_type(certificate)} public_key = certificate.public_key() key_attrs['size'] = str(public_key.key_size) if isinstance(public_key, EllipticCurvePublicKey): key_attrs['curve'] = public_key.curve.name else: key_attrs['exponent'] = str(public_key.public_numbers().e) elem_xml = Element('publicKey', attrib=key_attrs) cert_xml.append(elem_xml) dns_alt_names = CertificateUtils.get_dns_subject_alternative_names(certificate) if dns_alt_names: san_xml = Element('subjectAlternativeName') for dns_name in dns_alt_names: dns_xml = Element('DNS') dns_xml.text = dns_name san_xml.append(dns_xml) cert_xml.append(san_xml) cert_xml_list.append(cert_xml) return cert_xml_list
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 _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 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_printable_name(certificate.issuer), 'DigiCert SHA2 Extended Validation Server CA')
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 _object_to_json_dict(obj: Any) -> Union[bool, int, float, str, Dict[str, Any]]: """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 certificate = obj result = { # type: ignore # Add general info 'as_pem': obj.public_bytes(Encoding.PEM).decode('ascii'), 'hpkp_pin': CertificateUtils.get_hpkp_pin(obj), # Add some of the fields of the cert 'subject': CertificateUtils.get_name_as_text(certificate.subject), 'issuer': CertificateUtils.get_name_as_text(certificate.issuer), 'serialNumber': str(certificate.serial_number), 'notBefore': certificate.not_valid_before.strftime("%Y-%m-%d %H:%M:%S"), 'notAfter': certificate.not_valid_after.strftime("%Y-%m-%d %H:%M:%S"), 'signatureAlgorithm': certificate.signature_hash_algorithm.name, 'publicKey': { 'algorithm': CertificateUtils.get_public_key_type(certificate) }, } dns_alt_names = CertificateUtils.get_dns_subject_alternative_names(certificate) if dns_alt_names: result['subjectAlternativeName'] = {'DNS': dns_alt_names} # type: ignore # Add some info about the public key public_key = certificate.public_key() if isinstance(public_key, EllipticCurvePublicKey): result['publicKey']['size'] = str(public_key.curve.key_size) # type: ignore result['publicKey']['curve'] = public_key.curve.name # type: ignore else: result['publicKey']['size'] = str(public_key.key_size) result['publicKey']['exponent'] = str(public_key.public_numbers().e) elif isinstance(obj, object): # Some objects (like str) don't have a __dict__ if hasattr(obj, '__dict__'): result = {} for key, value in obj.__dict__.items(): # Remove private attributes if key.startswith('_'): continue result[key] = _object_to_json_dict(value) else: # Simple object like a bool result = obj else: raise TypeError('Unknown type: {}'.format(repr(obj))) return result
def __init__( self, server_info, # type: ServerConnectivityInfo scan_command, # type: HttpHeadersScanCommand raw_hsts_header, # type: Text raw_hpkp_header, # type: Text hpkp_report_only, # type: bool cert_chain # type: List[cryptography.x509.Certificate] ): # type: (...) -> None super(HttpHeadersScanResult, self).__init__(server_info, scan_command) self.hsts_header = ParsedHstsHeader(raw_hsts_header) if raw_hsts_header else None self.hpkp_header = ParsedHpkpHeader(raw_hpkp_header, hpkp_report_only) if raw_hpkp_header else None self.verified_certificate_chain = [] try: main_trust_store = TrustStoresRepository.get_default().get_main_store() self.verified_certificate_chain = main_trust_store.build_verified_certificate_chain(cert_chain) except CouldNotBuildVerifiedChainError: pass # Is the pinning configuration valid? self.is_valid_pin_configured = None self.is_backup_pin_configured = None if self.verified_certificate_chain and self.hpkp_header: # Is one of the configured pins in the current server chain? self.is_valid_pin_configured = False server_pin_list = [CertificateUtils.get_hpkp_pin(cert) for cert in self.verified_certificate_chain] for pin in self.hpkp_header.pin_sha256_list: if pin in server_pin_list: self.is_valid_pin_configured = True break # Is a backup pin configured? self.is_backup_pin_configured = set(self.hpkp_header.pin_sha256_list) != set(server_pin_list)
def __init__( self, server_info: ServerConnectivityInfo, scan_command: HttpHeadersScanCommand, raw_hsts_header: Optional[str], raw_hpkp_header: Optional[str], raw_expect_ct_header: Optional[str], hpkp_report_only: bool, cert_chain: List[Certificate], ) -> None: super().__init__(server_info, scan_command) self.hsts_header = ParsedHstsHeader(raw_hsts_header) if raw_hsts_header else None self.hpkp_header = ParsedHpkpHeader(raw_hpkp_header, hpkp_report_only) if raw_hpkp_header else None self.expect_ct_header = ParsedExpectCTHeader(raw_expect_ct_header) if raw_expect_ct_header else None self.verified_certificate_chain: List[Certificate] = [] try: main_trust_store = TrustStoresRepository.get_default().get_main_store() self.verified_certificate_chain = main_trust_store.build_verified_certificate_chain(cert_chain) except CouldNotBuildVerifiedChainError: pass # Is the pinning configuration valid? self.is_valid_pin_configured = None self.is_backup_pin_configured = None if self.verified_certificate_chain and self.hpkp_header: # Is one of the configured pins in the current server chain? self.is_valid_pin_configured = False server_pin_list = [CertificateUtils.get_hpkp_pin(cert) for cert in self.verified_certificate_chain] for pin in self.hpkp_header.pin_sha256_list: if pin in server_pin_list: self.is_valid_pin_configured = True break # Is a backup pin configured? self.is_backup_pin_configured = set(self.hpkp_header.pin_sha256_list) != set(server_pin_list)
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 _get_basic_certificate_text(self): 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_printable_name(certificate.subject)), self._format_field( 'Issuer:', CertificateUtils.get_printable_name(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:', public_key.__class__.__name__), self._format_field('Key Size:', public_key.key_size) ] try: # Print the Public key exponent if there's one; EC public keys don't have one for example text_output.append( self._format_field( 'Exponent:', '{0} (0x{0:x})'.format(public_key.public_numbers().e))) except KeyError: 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 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 __init__( self, server_info: ServerConnectivityInfo, scan_command: HttpHeadersScanCommand, raw_hsts_header: Optional[str], raw_hpkp_header: Optional[str], raw_expect_ct_header: Optional[str], hpkp_report_only: bool, cert_chain: List[Certificate], ) -> None: super().__init__(server_info, scan_command) self.hsts_header = ParsedHstsHeader( raw_hsts_header) if raw_hsts_header else None self.hpkp_header = ParsedHpkpHeader( raw_hpkp_header, hpkp_report_only) if raw_hpkp_header else None self.expect_ct_header = ParsedExpectCtHeader( raw_expect_ct_header) if raw_expect_ct_header else None self.verified_certificate_chain: List[Certificate] = [] try: main_trust_store = TrustStoresRepository.get_default( ).get_main_store() self.verified_certificate_chain = main_trust_store.build_verified_certificate_chain( cert_chain) except CouldNotBuildVerifiedChainError: pass # Is the pinning configuration valid? self.is_valid_pin_configured = None self.is_backup_pin_configured = None if self.verified_certificate_chain and self.hpkp_header: # Is one of the configured pins in the current server chain? self.is_valid_pin_configured = False server_pin_list = [ CertificateUtils.get_hpkp_pin(cert) for cert in self.verified_certificate_chain ] for pin in self.hpkp_header.pin_sha256_list: if pin in server_pin_list: self.is_valid_pin_configured = True break # Is a backup pin configured? self.is_backup_pin_configured = set( self.hpkp_header.pin_sha256_list) != set(server_pin_list)
def __init__( self, server_info: ServerConnectivityInfo, scan_command: HttpHeadersScanCommand, strict_transport_security_header: Optional[ StrictTransportSecurityHeader], public_key_pins_header: Optional[PublicKeyPinsHeader], public_key_pins_report_only_header: Optional[ PublicKeyPinsReportOnlyHeader], expect_ct_header: Optional[ExpectCtHeader], verified_chain: Optional[List[Certificate]], ) -> None: super().__init__(server_info, scan_command) self.strict_transport_security_header = strict_transport_security_header self.public_key_pins_header = public_key_pins_header self.public_key_pins_report_only_header = public_key_pins_report_only_header self.expect_ct_header = expect_ct_header self.verified_certificate_chain = verified_chain # Is the pinning configuration valid? self.is_valid_pin_configured = None self.is_backup_pin_configured = None returned_hpkp_header = None if self.public_key_pins_header: returned_hpkp_header = self.public_key_pins_header elif self.public_key_pins_report_only_header: returned_hpkp_header = self.public_key_pins_report_only_header if self.verified_certificate_chain and returned_hpkp_header: # Is one of the configured pins in the current server chain? self.is_valid_pin_configured = False server_pin_list = [ CertificateUtils.get_hpkp_pin(cert) for cert in self.verified_certificate_chain ] for pin in returned_hpkp_header.pin_sha256_list: if pin in server_pin_list: self.is_valid_pin_configured = True break # Is a backup pin configured? self.is_backup_pin_configured = set( returned_hpkp_header.pin_sha256_list) != set(server_pin_list)
def _certificate_chain_to_xml(certificate_chain): # type: (List[cryptography.x509.Certificate]) -> List[Element] cert_xml_list = [] for certificate in certificate_chain: cert_xml = Element('certificate', attrib={ 'sha1Fingerprint': binascii.hexlify( certificate.fingerprint( hashes.SHA1())).decode('ascii'), 'hpkpSha256Pin': CertificateUtils.get_hpkp_pin(certificate) }) # Add the PEM cert cert_as_pem_xml = Element('asPEM') cert_as_pem_xml.text = certificate.public_bytes(Encoding.PEM) cert_xml.append(cert_as_pem_xml) cert_xml_list.append(cert_xml) return cert_xml_list
def __init__( self, server_info: ServerConnectivityInfo, scan_command: HttpHeadersScanCommand, strict_transport_security_header: Optional[StrictTransportSecurityHeader], public_key_pins_header: Optional[PublicKeyPinsHeader], public_key_pins_report_only_header: Optional[PublicKeyPinsReportOnlyHeader], expect_ct_header: Optional[ExpectCtHeader], verified_chain: Optional[List[Certificate]], ) -> None: super().__init__(server_info, scan_command) self.strict_transport_security_header = strict_transport_security_header self.public_key_pins_header = public_key_pins_header self.public_key_pins_report_only_header = public_key_pins_report_only_header self.expect_ct_header = expect_ct_header self.verified_certificate_chain = verified_chain # Is the pinning configuration valid? self.is_valid_pin_configured = None self.is_backup_pin_configured = None returned_hpkp_header = None if self.public_key_pins_header: returned_hpkp_header = self.public_key_pins_header elif self.public_key_pins_report_only_header: returned_hpkp_header = self.public_key_pins_report_only_header if self.verified_certificate_chain and returned_hpkp_header: # Is one of the configured pins in the current server chain? self.is_valid_pin_configured = False server_pin_list = [CertificateUtils.get_hpkp_pin(cert) for cert in self.verified_certificate_chain] for pin in returned_hpkp_header.pin_sha256_list: if pin in server_pin_list: self.is_valid_pin_configured = True break # Is a backup pin configured? self.is_backup_pin_configured = set(returned_hpkp_header.pin_sha256_list) != set(server_pin_list)
def get_distrust_timeline( cls, verified_certificate_chain: List[Certificate] ) -> Optional[SymantecDistrustTimelineEnum]: has_whitelisted_cert = False has_blacklisted_cert = False # Is there a Symantec root certificate in the chain? for certificate in verified_certificate_chain: key_hash = binascii.hexlify(CertificateUtils.get_public_key_sha256(certificate)).decode('ascii') if key_hash in cls._CA_KEYS_BLACKLIST: has_blacklisted_cert = True if key_hash in cls._CA_KEYS_WHITELIST: has_whitelisted_cert = True distrust_enum = None if has_blacklisted_cert and not has_whitelisted_cert: leaf_cert = verified_certificate_chain[0] if leaf_cert.not_valid_before < datetime(year=2016, month=6, day=1): distrust_enum = SymantecDistrustTimelineEnum.MARCH_2018 else: distrust_enum = SymantecDistrustTimelineEnum.SEPTEMBER_2018 return distrust_enum
def concurrent_scanner(hn): # Setup the server to scan and ensure it is online/reachable server_info = server_connectivity_tester(hn) if server_info is 'error': return # Run multiple scan commands concurrently. concurrent_scanner = ConcurrentScanner() # Queue some scan commands print('\nQueuing some commands...') concurrent_scanner.queue_scan_command(server_info, CertificateInfoScanCommand()) concurrent_scanner.queue_scan_command(server_info, Tlsv13ScanCommand()) concurrent_scanner.queue_scan_command(server_info, Tlsv12ScanCommand()) concurrent_scanner.queue_scan_command(server_info, Tlsv11ScanCommand()) concurrent_scanner.queue_scan_command(server_info, Tlsv10ScanCommand()) concurrent_scanner.queue_scan_command(server_info, Sslv30ScanCommand()) concurrent_scanner.queue_scan_command(server_info, Sslv20ScanCommand()) # Process the results print('\nProcessing results...') for scan_result in concurrent_scanner.get_results(): # これからスキャンする情報(コマンド)を表示 print( f'\nReceived result for "{scan_result.scan_command.get_title()}" ' f'on {scan_result.server_info.hostname}') # A scan command can fail (as a bug); it is returned as a PluginRaisedExceptionResult # スキャンコマンドのエラー if isinstance(scan_result, PluginRaisedExceptionScanResult): ##raise RuntimeError(f'Scan command failed: {scan_result.scan_command.get_title()}') print( f'Scan command failed: {scan_result.scan_command.get_title()}') continue # Each scan result has attributes with the information yo're looking for # All these attributes are documented within each scan command's module if isinstance(scan_result.scan_command, Sslv20ScanCommand): # Cipher suitesリスト(ssl2.0)を表示 for cipher in scan_result.accepted_cipher_list: print(f' {cipher.name}') sql = "INSERT INTO CipherSuite(hostname, SSL20) values(?, ?)" data = [(hn, cipher.name)] cur.executemany(sql, data) if isinstance(scan_result.scan_command, Sslv30ScanCommand): # Cipher suitesリスト(ssl3.0)を表示 for cipher in scan_result.accepted_cipher_list: print(f' {cipher.name}') sql = "INSERT INTO CipherSuite(hostname, SSL30) values(?, ?)" data = [(hn, cipher.name)] cur.executemany(sql, data) if isinstance(scan_result.scan_command, Tlsv10ScanCommand): # Cipher suitesリスト(tls1.0)を表示 for cipher in scan_result.accepted_cipher_list: print(f' {cipher.name}') sql = "INSERT INTO CipherSuite(hostname, TLS10) values(?, ?)" data = [(hn, cipher.name)] cur.executemany(sql, data) if isinstance(scan_result.scan_command, Tlsv11ScanCommand): # Cipher suitesリスト(tls1.1)を表示 for cipher in scan_result.accepted_cipher_list: print(f' {cipher.name}') sql = "INSERT INTO CipherSuite(hostname, TLS11) values(?, ?)" data = [(hn, cipher.name)] cur.executemany(sql, data) if isinstance(scan_result.scan_command, Tlsv12ScanCommand): # Cipher suitesリスト(tls1.2)を表示 for cipher in scan_result.accepted_cipher_list: print(f' {cipher.name}') sql = "INSERT INTO CipherSuite(hostname, TLS12) values(?, ?)" data = [(hn, cipher.name)] cur.executemany(sql, data) if isinstance(scan_result.scan_command, Tlsv13ScanCommand): # Cipher suitesリスト(tls1.3)を表示 for cipher in scan_result.accepted_cipher_list: print(f' {cipher.name}') sql = "INSERT INTO CipherSuite(hostname, TLS13) values(?, ?)" data = [(hn, cipher.name)] cur.executemany(sql, data) elif isinstance(scan_result.scan_command, CertificateInfoScanCommand): # Print the Common Names within the verified certificate chain # 証明書情報を表示 if not scan_result.verified_certificate_chain: print('Error: certificate chain is not trusted!') cur.execute("INSERT INTO CertInfo(hostname) values(?)", [hn]) else: print('Certificate chain common names:') for cert in scan_result.verified_certificate_chain: cert_common_names_check = cert.subject.get_attributes_for_oid( NameOID.COMMON_NAME) if cert_common_names_check: cert_common_names = cert_common_names_check[0].value else: cert_common_names = '' cert_publickey = CertificateUtils.get_public_key_type(cert) cert_keysize = cert.public_key().key_size cert_sig_algo = cert.signature_algorithm_oid cert_leaf_ev = scan_result.leaf_certificate_is_ev # leafのみ # Policy type 判定未完成↓ """ try: cert_policy = cert.extensions.get_extension_for_oid(ExtensionOID.CERTIFICATE_POLICIES).value except ExtensionNotFound: continue OV = '2.23.140.1.2.2' DV = '2.23.140.1.2.1' if OV in cert_policy: cert_policy_type = 'OV' if DV in cert_policy: cert_policy_type = 'DV' else: cert_policy_type = '' """ cert_ov_check = cert.subject.get_attributes_for_oid( NameOID.ORGANIZATION_NAME) if cert_ov_check: cert_ov = cert_ov_check[0].value else: cert_ov = '' print(f' {cert_common_names}') print(f' {cert_publickey}') print(f' {cert_keysize}') print(f' {cert_sig_algo._name}') # print(f' {cert_policy_type}') print(f' {cert_leaf_ev}') print(f' {cert_ov}') sql = "INSERT INTO CertInfo(hostname, commonname, publickey, keysize, signature, certtype, ov) values(?, ?, ?, ?, ?, ?, ?)" data = [ (hn, cert_common_names, cert_publickey, cert_keysize, cert_sig_algo._name, cert_leaf_ev, cert_ov) ] cur.executemany(sql, data)
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) -> 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 __init__( self, server_info: ServerConnectivityInfo, scan_command: CertificateInfoScanCommand, certificate_chain: List[Certificate], path_validation_result_list: List[PathValidationResult], path_validation_error_list: List[PathValidationError], ocsp_response: OcspResponse ) -> None: super().__init__(server_info, scan_command) # Find the first trust store that successfully validated the certificate chain self.successful_trust_store = None # Sort the path_validation_result_list so the same successful_trust_store always get picked for a given server # because threading timings change the order of path_validation_result_list def sort_function(path_validation: PathValidationResult) -> str: return path_validation.trust_store.name.lower() path_validation_result_list.sort(key=sort_function) for path_result in path_validation_result_list: if path_result.is_certificate_trusted: self.successful_trust_store = path_result.trust_store self.ocsp_response = None self.is_ocsp_response_trusted = None self.ocsp_response_status = None if ocsp_response: self.ocsp_response_status = ocsp_response.status # We only keep the dictionary as a nassl.OcspResponse is not pickable self.ocsp_response = ocsp_response.as_dict() if self.successful_trust_store and self.ocsp_response_status == OcspResponseStatusEnum.SUCCESSFUL: try: ocsp_response.verify(self.successful_trust_store.path) self.is_ocsp_response_trusted = True except OcspResponseNotTrustedError: self.is_ocsp_response_trusted = False self.certificate_chain = certificate_chain # Check if it is EV - we only have the EV OIDs for Mozilla self.is_leaf_certificate_ev = TrustStoresRepository.get_default().get_main_store().is_extended_validation( self.certificate_chain[0] ) # Look for the Must-Staple extension has_must_staple = CertificateUtils.has_ocsp_must_staple_extension(self.certificate_chain[0]) self.certificate_has_must_staple_extension = has_must_staple # Look for the certificate transparency extension self.certificate_included_scts_count = CertificateUtils.count_scts_in_sct_extension(self.certificate_chain[0]) # Try to build the verified chain self.verified_certificate_chain: List[Certificate] = [] self.is_certificate_chain_order_valid = True if self.successful_trust_store: try: self.verified_certificate_chain = self.successful_trust_store.build_verified_certificate_chain( self.certificate_chain ) except InvalidCertificateChainOrderError: self.is_certificate_chain_order_valid = False except AnchorCertificateNotInTrustStoreError: pass self.has_anchor_in_certificate_chain = None if self.verified_certificate_chain: self.has_anchor_in_certificate_chain = self.verified_certificate_chain[-1] in self.certificate_chain self.path_validation_result_list = path_validation_result_list self.path_validation_error_list = path_validation_error_list try: CertificateUtils.matches_hostname(certificate_chain[0], server_info.tls_server_name_indication) self.certificate_matches_hostname = True except CertificateError: self.certificate_matches_hostname = False # Check if a SHA1-signed certificate is in the chain # Root certificates can still be signed with SHA1 so we only check leaf and intermediate certificates self.has_sha1_in_certificate_chain = None if self.verified_certificate_chain: self.has_sha1_in_certificate_chain = False for cert in self.verified_certificate_chain[:-1]: if isinstance(cert.signature_hash_algorithm, hashes.SHA1): self.has_sha1_in_certificate_chain = True break # Check if this is a distrusted Symantec-issued chain self.symantec_distrust_timeline = _SymantecDistructTester.get_distrust_timeline(self.verified_certificate_chain)
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): text_output = [self._format_title(self.COMMAND_TITLE)] text_output.extend(self._get_basic_certificate_text()) # Trust section text_output.extend(['', self._format_title('Certificate - 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_printable_name(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_printable_name(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_title('Certificate - 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
def __init__( self, server_info, # type: ServerConnectivityInfo scan_command, # type: CertificateInfoScanCommand certificate_chain, # type: List[cryptography.x509.Certificate] path_validation_result_list, # type: List[PathValidationResult] path_validation_error_list, # type: List[PathValidationError] ocsp_response # type: OcspResponse ): # type: (...) -> None super(CertificateInfoScanResult, self).__init__(server_info, scan_command) # Find the first trust store that successfully validated the certificate chain self.successful_trust_store = None # Sort the path_validation_result_list so the same successful_trust_store always get picked for a given server # because threading timings change the order of path_validation_result_list def sort_function(path_validation): # type: (PathValidationResult) -> Text return path_validation.trust_store.name.lower() path_validation_result_list.sort(key=sort_function) for path_result in path_validation_result_list: if path_result.is_certificate_trusted: self.successful_trust_store = path_result.trust_store self.ocsp_response = None self.is_ocsp_response_trusted = None if ocsp_response: # We only keep the dictionary as a nassl.OcspResponse is not pickable self.ocsp_response = ocsp_response.as_dict() if self.successful_trust_store: try: ocsp_response.verify(self.successful_trust_store.path) self.is_ocsp_response_trusted = True except OcspResponseNotTrustedError: self.is_ocsp_response_trusted = False self.certificate_chain = certificate_chain # Check if it is EV - we only have the EV OIDs for Mozilla self.is_leaf_certificate_ev = TrustStoresRepository.get_main( ).is_extended_validation(self.certificate_chain[0]) # Try to build the verified chain self.verified_certificate_chain = [] self.is_certificate_chain_order_valid = True if self.successful_trust_store: try: self.verified_certificate_chain = self.successful_trust_store.build_verified_certificate_chain( self.certificate_chain) except InvalidCertificateChainOrderError: self.is_certificate_chain_order_valid = False except AnchorCertificateNotInTrustStoreError: pass self.has_anchor_in_certificate_chain = None if self.verified_certificate_chain: self.has_anchor_in_certificate_chain = self.verified_certificate_chain[ -1] in self.certificate_chain self.path_validation_result_list = path_validation_result_list self.path_validation_error_list = path_validation_error_list try: CertificateUtils.matches_hostname( certificate_chain[0], server_info.tls_server_name_indication) self.certificate_matches_hostname = True except CertificateError: self.certificate_matches_hostname = False # Check if a SHA1-signed certificate is in the chain # Root certificates can still be signed with SHA1 so we only check leaf and intermediate certificates self.has_sha1_in_certificate_chain = None if self.verified_certificate_chain: self.has_sha1_in_certificate_chain = False for cert in self.verified_certificate_chain[:-1]: if isinstance(cert.signature_hash_algorithm, hashes.SHA1): self.has_sha1_in_certificate_chain = True break
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_printable_name(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 perform(self) -> CertificateChainDeploymentAnalysisResult: """Run the analysis. """ leaf_cert = self.received_certificate_chain[0] # OCSP Must-Staple has_ocsp_must_staple = False try: tls_feature_ext = leaf_cert.extensions.get_extension_for_oid(ExtensionOID.TLS_FEATURE) for feature_type in tls_feature_ext.value: if feature_type == cryptography.x509.TLSFeatureType.status_request: has_ocsp_must_staple = True break except ExtensionNotFound: pass # Received chain order is_chain_order_valid = True previous_issuer = None for index, cert in enumerate(self.received_certificate_chain): current_subject = cert.subject if index > 0: # Compare the current subject with the previous issuer in the chain if current_subject != previous_issuer: is_chain_order_valid = False break try: previous_issuer = cert.issuer except KeyError: # Missing issuer; this is okay if this is the last cert previous_issuer = u"missing issuer {}".format(index) # Check if it is EV - we only have the EV OIDs for Mozilla is_leaf_certificate_ev = TrustStoresRepository.get_default().get_main_store().is_extended_validation( self.received_certificate_chain[0] ) # Check for Signed Timestamps number_of_scts: Optional[int] = 0 try: # Look for the x509 extension sct_ext = leaf_cert.extensions.get_extension_for_oid( ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS ) if isinstance(sct_ext.value, cryptography.x509.UnrecognizedExtension): # The version of OpenSSL on the system is too old and can't parse the SCT extension number_of_scts = None # Count the number of entries in the extension number_of_scts = len(sct_ext.value) except ExtensionNotFound: pass # Check if the anchor was sent by the server has_anchor_in_certificate_chain = None if self.verified_certificate_chain: has_anchor_in_certificate_chain = self.verified_certificate_chain[-1] in self.received_certificate_chain # Check hostname validation try: CertificateUtils.matches_hostname(leaf_cert, self.server_hostname) certificate_matches_hostname = True except CertificateError: certificate_matches_hostname = False # Check if a SHA1-signed certificate is in the chain # Root certificates can still be signed with SHA1 so we only check leaf and intermediate certificates has_sha1_in_certificate_chain = None if self.verified_certificate_chain: has_sha1_in_certificate_chain = False for cert in self.verified_certificate_chain[:-1]: if isinstance(cert.signature_hash_algorithm, hashes.SHA1): has_sha1_in_certificate_chain = True break # Check if this is a distrusted Symantec-issued chain verified_chain_has_legacy_symantec_anchor = None if self.verified_certificate_chain: symantec_distrust_timeline = _SymantecDistructTester.get_distrust_timeline(self.verified_certificate_chain) verified_chain_has_legacy_symantec_anchor = True if symantec_distrust_timeline else False # Check the OCSP response if there is one is_ocsp_response_trusted = None ocsp_response_status = None if self.received_ocsp_response: ocsp_response_status = self.received_ocsp_response.status if self.trust_store_used_to_build_verified_chain \ and ocsp_response_status == OcspResponseStatusEnum.SUCCESSFUL: try: self.received_ocsp_response.verify(self.trust_store_used_to_build_verified_chain.path) is_ocsp_response_trusted = True except OcspResponseNotTrustedError: is_ocsp_response_trusted = False return CertificateChainDeploymentAnalysisResult( leaf_certificate_subject_matches_hostname=certificate_matches_hostname, leaf_certificate_has_must_staple_extension=has_ocsp_must_staple, leaf_certificate_is_ev=is_leaf_certificate_ev, leaf_certificate_signed_certificate_timestamps_count=number_of_scts, received_chain_contains_anchor_certificate=has_anchor_in_certificate_chain, received_chain_has_valid_order=is_chain_order_valid, verified_chain_has_sha1_signature=has_sha1_in_certificate_chain, verified_chain_has_legacy_symantec_anchor=verified_chain_has_legacy_symantec_anchor, ocsp_response_is_trusted=is_ocsp_response_trusted, ocsp_response_status=ocsp_response_status, )
def as_xml(self): xml_output = Element(self.scan_command.get_cli_argument(), title=self.COMMAND_TITLE) # Certificate chain cert_xml_list = [] for index, certificate in enumerate(self.certificate_chain, start=0): cert_xml = Element('certificate', attrib={ 'sha1Fingerprint': binascii.hexlify( certificate.fingerprint( hashes.SHA1())).decode('ascii'), 'position': 'leaf' if index == 0 else 'intermediate', 'suppliedServerNameIndication': self.server_info.tls_server_name_indication, 'hpkpSha256Pin': CertificateUtils.get_hpkp_pin(certificate) }) # Add the PEM cert cert_as_pem_xml = Element('asPEM') cert_as_pem_xml.text = certificate.public_bytes(Encoding.PEM) cert_xml.append(cert_as_pem_xml) cert_chain_attrs = { 'isChainOrderValid': str(self.is_certificate_chain_order_valid) } if self.verified_certificate_chain: cert_chain_attrs['containsAnchorCertificate'] = str(False) if not self.has_anchor_in_certificate_chain \ else str(True) cert_chain_xml = Element('receivedCertificateChain', attrib=cert_chain_attrs) for cert_xml in cert_xml_list: cert_chain_xml.append(cert_xml) xml_output.append(cert_chain_xml) # Trust trust_validation_xml = Element('certificateValidation') # Hostname validation host_validation_xml = Element( 'hostnameValidation', serverHostname=self.server_info.tls_server_name_indication, certificateMatchesServerHostname=str( self.certificate_matches_hostname)) trust_validation_xml.append(host_validation_xml) # Path validation that was successful for path_result in self.path_validation_result_list: path_attrib_xml = { 'usingTrustStore': path_result.trust_store.name, 'trustStoreVersion': path_result.trust_store.version, 'validationResult': path_result.verify_string } # Things we only do with the Mozilla store verified_cert_chain_xml = None if 'Mozilla' in path_result.trust_store.name: # EV certs if self.is_leaf_certificate_ev: path_attrib_xml['isExtendedValidationCertificate'] = str( self.is_leaf_certificate_ev) # Verified chain if self.verified_certificate_chain: verified_cert_chain_xml = Element( 'verifiedCertificateChain', { 'hasSha1SignedCertificate': str(self.has_sha1_in_certificate_chain) }) for certificate in self.certificate_chain: cert_xml = Element( 'certificate', attrib={ 'sha1Fingerprint': binascii.hexlify( certificate.fingerprint( hashes.SHA1())).decode('ascii'), 'suppliedServerNameIndication': self.server_info.tls_server_name_indication, 'hpkpSha256Pin': CertificateUtils.get_hpkp_pin(certificate) }) # Add the PEM cert cert_as_pem_xml = Element('asPEM') cert_as_pem_xml.text = certificate.public_bytes( Encoding.PEM) cert_xml.append(cert_as_pem_xml) verified_cert_chain_xml.append(cert_xml) path_valid_xml = Element('pathValidation', attrib=path_attrib_xml) if verified_cert_chain_xml is not None: path_valid_xml.append(verified_cert_chain_xml) trust_validation_xml.append(path_valid_xml) # Path validation that ran into errors for path_error in self.path_validation_error_list: error_txt = 'ERROR: {}'.format(path_error.error_message) path_attrib_xml = { 'usingTrustStore': path_result.trust_store.name, 'trustStoreVersion': path_result.trust_store.version, 'error': error_txt } trust_validation_xml.append( Element('pathValidation', attrib=path_attrib_xml)) xml_output.append(trust_validation_xml) # OCSP Stapling ocsp_xml = Element( 'ocspStapling', attrib={ 'isSupported': 'False' if self.ocsp_response is None else 'True' }) if self.ocsp_response: ocsp_resp_xmp = Element('ocspResponse', attrib={ 'isTrustedByMozillaCAStore': str(self.is_ocsp_response_trusted) }) responder_xml = Element('responderID') responder_xml.text = self.ocsp_response['responderID'] ocsp_resp_xmp.append(responder_xml) produced_xml = Element('producedAt') produced_xml.text = self.ocsp_response['producedAt'] ocsp_resp_xmp.append(produced_xml) response_status_xml = Element('responseStatus') response_status_xml.text = self.ocsp_response['responseStatus'] ocsp_resp_xmp.append(response_status_xml) ocsp_xml.append(ocsp_resp_xmp) xml_output.append(ocsp_xml) # All done return xml_output
def sslyzeScan(threadname, connection, url): logfile_connection = 'log/' + scriptname + '-error-connections.log' logfile_other = 'log/' + scriptname + '-error-other.log' logfile_scan = 'log/' + scriptname + '-error-other.log' has_ip = 0 is_reachable = 0 has_ip = getIP(url) if has_ip: try: server_tester = ServerConnectivityTester(hostname=url) server_info = server_tester.perform() is_reachable = 1 except ServerConnectivityError: with open(logfile_connection, 'a') as log: log.write('Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not connect to host: ' + url + '\n') except Exception: with open(logfile_other, 'a') as log: log.write('Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Thrown error for host: ' + url + '\n') else: with open(logfile_connection, 'a') as log: log.write('Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not resolve host: ' + url + '\n') if (is_reachable): concurrent_scanner = ConcurrentScanner() concurrent_scanner.queue_scan_command(server_info, Sslv20ScanCommand()) concurrent_scanner.queue_scan_command(server_info, Sslv30ScanCommand()) concurrent_scanner.queue_scan_command(server_info, Tlsv10ScanCommand()) concurrent_scanner.queue_scan_command(server_info, Tlsv11ScanCommand()) concurrent_scanner.queue_scan_command(server_info, Tlsv12ScanCommand()) concurrent_scanner.queue_scan_command(server_info, Tlsv13ScanCommand()) concurrent_scanner.queue_scan_command(server_info, HeartbleedScanCommand()) concurrent_scanner.queue_scan_command(server_info, HttpHeadersScanCommand()) concurrent_scanner.queue_scan_command(server_info, CertificateInfoScanCommand()) # Process the results for scan_result in concurrent_scanner.get_results(): # Sometimes a scan command can unexpectedly fail (as a bug); it is returned as a PluginRaisedExceptionResult if isinstance(scan_result, PluginRaisedExceptionScanResult): with open(logfile_scan, 'a') as log: log.write('Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', Scan command failed: {}'.format( scan_result.as_text()) + '\n') if isinstance(scan_result.scan_command, Sslv20ScanCommand): ssl_version = "sslv2" try: if len(scan_result.accepted_cipher_list) == 0: supports_sslv2 = 0 else: supports_sslv2 = 1 for cipher in scan_result.accepted_cipher_list: cipher = (u'{}'.format(cipher.name)) sql_command = ( 'insert into ' + tbl_supported_ciphers + '(url,cipher,version) values (%s,%s,%s)') sql_data = (url, cipher, ssl_version) cur0 = connection.cursor() cur0.execute(sql_command, sql_data) connection.commit() cur0.close() except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get sslv2 attributes for host: ' + url + '\n') supports_sslv2 = 0 if isinstance(scan_result.scan_command, Sslv30ScanCommand): ssl_version = "sslv3" try: if len(scan_result.accepted_cipher_list) == 0: supports_sslv3 = 0 else: supports_sslv3 = 1 for cipher in scan_result.accepted_cipher_list: cipher = (u'{}'.format(cipher.name)) sql_command = ( 'insert into ' + tbl_supported_ciphers + '(url,cipher,version) values (%s,%s,%s)') sql_data = (url, cipher, ssl_version) cur0 = connection.cursor() cur0.execute(sql_command, sql_data) connection.commit() cur0.close() except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get sslv3 attributes for host: ' + url + '\n') supports_sslv3 = 0 if isinstance(scan_result.scan_command, Tlsv10ScanCommand): ssl_version = "tlsv10" try: if len(scan_result.accepted_cipher_list) == 0: supports_tlsv10 = 0 else: supports_tlsv10 = 1 for cipher in scan_result.accepted_cipher_list: cipher = (u'{}'.format(cipher.name)) sql_command = ( 'insert into ' + tbl_supported_ciphers + '(url,cipher,version) values (%s,%s,%s)') sql_data = (url, cipher, ssl_version) cur0 = connection.cursor() cur0.execute(sql_command, sql_data) connection.commit() cur0.close() except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get tlsv10 attributes for host: ' + url + '\n') supports_tlsv10 = 0 if isinstance(scan_result.scan_command, Tlsv11ScanCommand): ssl_version = "tlsv11" try: if len(scan_result.accepted_cipher_list) == 0: supports_tlsv11 = 0 else: supports_tlsv11 = 1 for cipher in scan_result.accepted_cipher_list: cipher = (u'{}'.format(cipher.name)) sql_command = ( 'insert into ' + tbl_supported_ciphers + '(url,cipher,version) values (%s,%s,%s)') sql_data = (url, cipher, ssl_version) cur0 = connection.cursor() cur0.execute(sql_command, sql_data) connection.commit() cur0.close() except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get tlsv11 attributes for host: ' + url + '\n') supports_tlsv11 = 0 if isinstance(scan_result.scan_command, Tlsv12ScanCommand): ssl_version = "tlsv12" try: if len(scan_result.accepted_cipher_list) == 0: supports_tlsv12 = 0 else: supports_tlsv12 = 1 for cipher in scan_result.accepted_cipher_list: cipher = (u'{}'.format(cipher.name)) sql_command = ( 'insert into ' + tbl_supported_ciphers + '(url,cipher,version) values (%s,%s,%s)') sql_data = (url, cipher, ssl_version) cur0 = connection.cursor() cur0.execute(sql_command, sql_data) connection.commit() cur0.close() except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get tlsv12 attributes for host: ' + url + '\n') supports_tlsv12 = 0 if isinstance(scan_result.scan_command, Tlsv13ScanCommand): ssl_version = "tlsv13" try: if len(scan_result.accepted_cipher_list) == 0: supports_tlsv13 = 0 else: supports_tlsv13 = 1 for cipher in scan_result.accepted_cipher_list: cipher = (u'{}'.format(cipher.name)) sql_command = ( 'insert into ' + tbl_supported_ciphers + '(url,cipher,version) values (%s,%s,%s)') sql_data = (url, cipher, ssl_version) cur0 = connection.cursor() cur0.execute(sql_command, sql_data) connection.commit() cur0.close() except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get tlsv13 attributes for host: ' + url + '\n') supports_tlsv13 = 0 if isinstance(scan_result.scan_command, HeartbleedScanCommand): vulnerable_heartbleed = 0 try: if (scan_result.is_vulnerable_to_heartbleed): vulnerable_heartbleed = 1 except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get heartbleed attribute for host: ' + url + '\n') vulnerable_heartbleed = 0 if isinstance(scan_result.scan_command, HttpHeadersScanCommand): hsts_preload_set = 0 hsts_include_subdomains_set = 0 hsts_max_age_set = 0 hsts_supported = 0 hpkp_supported = 0 try: if (scan_result.hsts_header): hsts_supported = 1 if (scan_result.hsts_header.preload): hsts_preload_set = 1 if (scan_result.hsts_header.include_subdomains ) == True: hsts_include_subdomains_set = 1 hsts_max_age_set = scan_result.hsts_header.max_age except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get hsts attributes for host: ' + url + '\n') hsts_preload_set = 0 hsts_include_subdomains_set = 0 hsts_max_age_set = 0 hsts_supported = 0 try: if (scan_result.hpkp_header): hpkp_supported = 1 except AttributeError: hpkp_supported = 0 if isinstance(scan_result.scan_command, CertificateInfoScanCommand): chain_is_trusted = 0 try: if (scan_result.verified_certificate_chain): chain_is_trusted = 1 except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get hpkp attributes for host: ' + url + '\n') chain_is_trusted = 0 cert_matches_hostname = 0 cert_is_ev = False try: CertificateUtils.matches_hostname( scan_result.certificate_chain[0], server_info.tls_server_name_indication) cert_matches_hostname = 1 except CertificateError: cert_matches_hostname = 0 except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get certificate_chain attribute for host: ' + url + '\n') try: cert_is_ev = TrustStoresRepository.get_default( ).get_main_store().is_extended_validation( scan_result.certificate_chain[0]) except AttributeError: with open(logfile_scan, 'a') as log: log.write( 'Error, ' + time.strftime("%Y-%m-%d %H:%M:%S") + ', ' + threadname + ': Could not get extended_validation attribute for host: ' + url + '\n') sql_cmd = ( 'insert into ' + tbl_ssl_options + '(url,heartbleed_vulnerable,hsts_supported,hsts_preload_set,hsts_include_subdomains_set,hsts_max_age_set,hpkp_supported,chain_is_trusted,match_hostname,is_ev,sslv2,sslv3,tlsv10,tlsv11,tlsv12,tlsv13) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)' ) sql_dat = (url, vulnerable_heartbleed, hsts_supported, hsts_preload_set, hsts_include_subdomains_set, hsts_max_age_set, hpkp_supported, chain_is_trusted, cert_matches_hostname, cert_is_ev, supports_sslv2, supports_sslv3, supports_tlsv10, supports_tlsv11, supports_tlsv12, supports_tlsv13) cur = connection.cursor() cur.execute(sql_cmd, sql_dat) connection.commit() cur.close()
def default(self, obj: Any) -> Union[bool, int, float, str, Dict[str, Any]]: result: Union[bool, int, float, str, Dict[str, Any]] if isinstance(obj, Enum): result = obj.name elif isinstance(obj, ObjectIdentifier): result = obj.dotted_string elif isinstance(obj, x509._Certificate): certificate = obj result = { # Add general info "as_pem": obj.public_bytes(Encoding.PEM).decode("ascii"), "hpkp_pin": CertificateUtils.get_hpkp_pin(obj), # Add some of the fields of the cert "subject": CertificateUtils.get_name_as_text(certificate.subject), "issuer": CertificateUtils.get_name_as_text(certificate.issuer), "serialNumber": str(certificate.serial_number), "notBefore": certificate.not_valid_before.strftime("%Y-%m-%d %H:%M:%S"), "notAfter": certificate.not_valid_after.strftime("%Y-%m-%d %H:%M:%S"), "signatureAlgorithm": certificate.signature_hash_algorithm.name, "publicKey": { "algorithm": CertificateUtils.get_public_key_type(certificate) }, } dns_alt_names = CertificateUtils.get_dns_subject_alternative_names( certificate) if dns_alt_names: result["subjectAlternativeName"] = { "DNS": dns_alt_names } # type: ignore # Add some info about the public key public_key = certificate.public_key() if isinstance(public_key, EllipticCurvePublicKey): result["publicKey"]["size"] = str( public_key.curve.key_size) # type: ignore result["publicKey"][ "curve"] = public_key.curve.name # type: ignore else: result["publicKey"]["size"] = str(public_key.key_size) result["publicKey"]["exponent"] = str( public_key.public_numbers().e) elif isinstance(obj, Path): result = str(obj) elif isinstance(obj, object): # Some objects (like str) don't have a __dict__ if hasattr(obj, "__dict__"): result = {} for key, value in obj.__dict__.items(): # Remove private attributes if key.startswith("_"): continue result[key] = self.default(value) else: # Simple object like a bool result = obj # type: ignore 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()), ''] 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