Exemple #1
0
    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
Exemple #2
0
    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
Exemple #3
0
    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
Exemple #4
0
    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
Exemple #6
0
    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
Exemple #9
0
    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
Exemple #10
0
    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
Exemple #11
0
    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
Exemple #12
0
        # 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())

Exemple #13
0
        # 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
Exemple #16
0
        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