Example #1
0
    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)
Example #2
0
    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)
Example #3
0
    def test(self):
        intermediate_path = os.path.join(
            os.path.dirname(__file__), '..', 'utils',
            'DigiCertSHA2ExtendedValidationServerCA.pem')
        with open(intermediate_path) as intermediate_file:
            intermediate_pem = intermediate_file.read().encode('ascii')

        leaf_path = os.path.join(os.path.dirname(__file__), '..', 'utils',
                                 'github.com.pem')
        with open(leaf_path) as leaf_file:
            leaf_pem = leaf_file.read().encode('ascii')

        certificate_chain = [
            load_pem_x509_certificate(leaf_pem, default_backend()),
            load_pem_x509_certificate(intermediate_pem, default_backend())
        ]

        found_mozilla = False
        for trust_store in TrustStoresRepository.get_default().get_all_stores(
        ):
            verified_chain = trust_store.build_verified_certificate_chain(
                certificate_chain)
            self.assertTrue(verified_chain)
            if trust_store.name == 'Mozilla':
                found_mozilla = True
                # The GH certificate is EV
                self.assertTrue(
                    trust_store.is_extended_validation(certificate_chain[0]))

        self.assertTrue(found_mozilla)
Example #4
0
    def process_task(
            self,
            server_info: ServerConnectivityInfo,
            scan_command: PluginScanCommand
    ) -> 'CertificateInfoScanResult':
        if not isinstance(scan_command, CertificateInfoScanCommand):
            raise ValueError('Unexpected scan command')

        final_trust_store_list = TrustStoresRepository.get_default().get_all_stores()
        if scan_command.custom_ca_file:
            if not os.path.isfile(scan_command.custom_ca_file):
                raise ValueError('Could not open supplied CA file at "{}"'.format(scan_command.custom_ca_file))
            final_trust_store_list.append(TrustStore(scan_command.custom_ca_file, 'Custom --ca_file', 'N/A'))

        # Workaround for https://github.com/pyca/cryptography/issues/3495
        default_backend()

        thread_pool = ThreadPool()
        for trust_store in final_trust_store_list:
            # Try to connect with each trust store
            thread_pool.add_job((self._get_and_verify_certificate_chain, [server_info, trust_store]))

        # Start processing the jobs; one thread per trust
        thread_pool.start(len(final_trust_store_list))

        # Store the results as they come
        certificate_chain: List[Certificate] = []
        path_validation_result_list = []
        path_validation_error_list = []
        ocsp_response = None

        for (job, result) in thread_pool.get_result():
            (_, (_, trust_store)) = job
            certificate_chain, validation_result, _ocsp_response = result

            # Keep the OCSP response if the validation was succesful and a response was returned
            if _ocsp_response:
                ocsp_response = _ocsp_response

            # Store the returned verify string for each trust store
            path_validation_result_list.append(PathValidationResult(trust_store, validation_result))

        # Store thread pool errors
        last_exception = None
        for (job, exception) in thread_pool.get_error():
            (_, (_, trust_store)) = job
            path_validation_error_list.append(PathValidationError(trust_store, exception))
            last_exception = exception

        thread_pool.join()

        if len(path_validation_error_list) == len(final_trust_store_list):
            # All connections failed unexpectedly; raise an exception instead of returning a result
            raise last_exception  # type: ignore

        # All done
        return CertificateInfoScanResult(server_info, scan_command, certificate_chain, path_validation_result_list,
                                         path_validation_error_list, ocsp_response)
Example #5
0
    def process_task(
            self,
            server_info: ServerConnectivityInfo,
            scan_command: PluginScanCommand
    ) -> 'CertificateInfoScanResult':
        if not isinstance(scan_command, CertificateInfoScanCommand):
            raise ValueError('Unexpected scan command')

        final_trust_store_list = TrustStoresRepository.get_default().get_all_stores()
        if scan_command.custom_ca_file:
            if not os.path.isfile(scan_command.custom_ca_file):
                raise ValueError('Could not open supplied CA file at "{}"'.format(scan_command.custom_ca_file))
            final_trust_store_list.append(TrustStore(scan_command.custom_ca_file, 'Custom --ca_file', 'N/A'))

        # Workaround for https://github.com/pyca/cryptography/issues/3495
        default_backend()

        thread_pool = ThreadPool()
        for trust_store in final_trust_store_list:
            # Try to connect with each trust store
            thread_pool.add_job((self._get_and_verify_certificate_chain, [server_info, trust_store]))

        # Start processing the jobs; one thread per trust
        thread_pool.start(len(final_trust_store_list))

        # Store the results as they come
        certificate_chain: List[Certificate] = []
        path_validation_result_list = []
        path_validation_error_list = []
        ocsp_response = None

        for (job, result) in thread_pool.get_result():
            (_, (_, trust_store)) = job
            certificate_chain, validation_result, _ocsp_response = result

            # Keep the OCSP response if the validation was succesful and a response was returned
            if _ocsp_response:
                ocsp_response = _ocsp_response

            # Store the returned verify string for each trust store
            path_validation_result_list.append(PathValidationResult(trust_store, validation_result))

        # Store thread pool errors
        last_exception = None
        for (job, exception) in thread_pool.get_error():
            (_, (_, trust_store)) = job
            path_validation_error_list.append(PathValidationError(trust_store, exception))
            last_exception = exception

        thread_pool.join()

        if len(path_validation_error_list) == len(final_trust_store_list):
            # All connections failed unexpectedly; raise an exception instead of returning a result
            raise last_exception  # type: ignore

        # All done
        return CertificateInfoScanResult(server_info, scan_command, certificate_chain, path_validation_result_list,
                                         path_validation_error_list, ocsp_response)
Example #6
0
    def process_task(
            self,
            server_info: ServerConnectivityInfo,
            scan_command: PluginScanCommand
    ) -> 'HttpHeadersScanResult':
        if not isinstance(scan_command, HttpHeadersScanCommand):
            raise ValueError('Unexpected scan command')

        if server_info.tls_wrapped_protocol not in [TlsWrappedProtocolEnum.PLAIN_TLS, TlsWrappedProtocolEnum.HTTPS]:
            raise ValueError('Cannot test for HTTP headers on a StartTLS connection.')

        # Perform the SSL handshake
        mozilla_store = TrustStoresRepository.get_default().get_main_store()
        ssl_connection = server_info.get_preconfigured_ssl_connection(ssl_verify_locations=mozilla_store.path)
        try:
            ssl_connection.connect()
            try:
                verified_chain_as_pem = ssl_connection.ssl_client.get_verified_chain()
            except CouldNotBuildVerifiedChain:
                verified_chain_as_pem = None

            # Send an HTTP GET request to the server
            ssl_connection.ssl_client.write(HttpRequestGenerator.get_request(host=server_info.hostname))

            # We do not follow redirections because the security headers must be set on the first page according to
            # https://hstspreload.appspot.com/:
            # "If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS
            # header (rather than the page it redirects to)."
            http_response = HttpResponseParser.parse_from_ssl_connection(ssl_connection.ssl_client)
        finally:
            ssl_connection.close()

        if http_response.version == 9:
            # HTTP 0.9 => Probably not an HTTP response
            raise ValueError('Server did not return an HTTP response')

        # Parse the certificate chain
        verified_chain = [
            load_pem_x509_certificate(cert_as_pem.encode('ascii'), backend=default_backend())
            for cert_as_pem in verified_chain_as_pem
        ] if verified_chain_as_pem else None

        # Parse each header
        hsts_header = StrictTransportSecurityHeader.from_http_response(http_response)
        expect_ct_header = ExpectCtHeader.from_http_response(http_response)
        hpkp_header = PublicKeyPinsHeader.from_http_response(http_response)
        hpkp_report_only_header = PublicKeyPinsReportOnlyHeader.from_http_response(http_response)

        return HttpHeadersScanResult(
            server_info,
            scan_command,
            hsts_header,
            hpkp_header,
            hpkp_report_only_header,
            expect_ct_header,
            verified_chain
        )
Example #7
0
    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)
Example #8
0
    def process_task(self, server_info, scan_command):
        # type: (ServerConnectivityInfo, CertificateInfoScanCommand) -> CertificateInfoScanResult
        final_trust_store_list = list(TrustStoresRepository.get_all())
        if scan_command.custom_ca_file:
            if not os.path.isfile(scan_command.custom_ca_file):
                raise ValueError(u'Could not open supplied CA file at "{}"'.format(scan_command.custom_ca_file))
            final_trust_store_list.append(TrustStore(scan_command.custom_ca_file, u'Custom --ca_file', u'N/A'))

        thread_pool = ThreadPool()
        for trust_store in final_trust_store_list:
            # Try to connect with each trust store
            thread_pool.add_job((self._get_and_verify_certificate_chain, (server_info, trust_store)))

        # Start processing the jobs; one thread per trust
        thread_pool.start(len(final_trust_store_list))

        # Store the results as they come
        certificate_chain = []
        path_validation_result_list = []
        path_validation_error_list = []
        ocsp_response = None

        for (job, result) in thread_pool.get_result():
            (_, (_, trust_store)) = job
            certificate_chain, validation_result, ocsp_response = result
            # Store the returned verify string for each trust store
            path_validation_result_list.append(PathValidationResult(trust_store, validation_result))

        # Store thread pool errors
        last_exception = None
        for (job, exception) in thread_pool.get_error():
            (_, (_, trust_store)) = job
            path_validation_error_list.append(PathValidationError(trust_store, exception))
            last_exception = exception

        thread_pool.join()

        if len(path_validation_error_list) == len(final_trust_store_list):
            # All connections failed unexpectedly; raise an exception instead of returning a result
            raise RuntimeError(u'Could not connect to the server; last error: {}'.format(last_exception))

        # All done
        return CertificateInfoScanResult(server_info, scan_command, certificate_chain, path_validation_result_list,
                                         path_validation_error_list, ocsp_response)
Example #9
0
    def test(self):
        intermediate_path = os.path.join(
            os.path.dirname(__file__), u'..', u'utils',
            u'DigiCertSHA2ExtendedValidationServerCA.pem')
        with open(intermediate_path) as intermediate_file:
            intermediate_pem = intermediate_file.read()

        leaf_path = os.path.join(os.path.dirname(__file__), u'..', u'utils',
                                 u'github.com.pem')
        with open(leaf_path) as leaf_file:
            leaf_pem = leaf_file.read()

        certificate_chain = [
            Certificate.from_pem(leaf_pem),
            Certificate.from_pem(intermediate_pem)
        ]

        for trust_store in TrustStoresRepository.get_all():
            verified_chain = trust_store.build_verified_certificate_chain(
                certificate_chain)
            self.assertTrue(verified_chain)
Example #10
0
    def test(self):
        intermediate_path = os.path.join(os.path.dirname(__file__), '..', 'utils',
                                         'DigiCertSHA2ExtendedValidationServerCA.pem')
        with open(intermediate_path) as intermediate_file:
            intermediate_pem = intermediate_file.read().encode('ascii')

        leaf_path = os.path.join(os.path.dirname(__file__), '..', 'utils', 'github.com.pem')
        with open(leaf_path) as leaf_file:
            leaf_pem = leaf_file.read().encode('ascii')

        certificate_chain = [load_pem_x509_certificate(leaf_pem, default_backend()),
                             load_pem_x509_certificate(intermediate_pem, default_backend())]

        found_mozilla = False
        for trust_store in TrustStoresRepository.get_default().get_all_stores():
            verified_chain = trust_store.build_verified_certificate_chain(certificate_chain)
            self.assertTrue(verified_chain)
            if trust_store.name == 'Mozilla':
                found_mozilla = True
                # The GH certificate is EV
                self.assertTrue(trust_store.is_extended_validation(certificate_chain[0]))

        self.assertTrue(found_mozilla)
Example #11
0
    def __init__(self, server_info, scan_command, raw_hsts_header,
                 raw_hpkp_header, hpkp_report_only, cert_chain):
        # type: (ServerConnectivityInfo, HttpHeadersScanCommand, Text, Text, bool, List[X509Certificate]) -> 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

        parsed_certificate_chain = [
            Certificate.from_nassl(x509_cert) for x509_cert in cert_chain
        ]
        self.verified_certificate_chain = []
        try:
            self.verified_certificate_chain = TrustStoresRepository.get_main(
            ).build_verified_certificate_chain(parsed_certificate_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 = [
                cert.hpkp_pin 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)
Example #12
0
    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)
    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,
        )
Example #14
0
    def parse_command_line(self) -> Tuple[List[ServerConnectivityTester], List[ServerStringParsingError], Any]:
        """Parses the command line used to launch SSLyze.
        """
        (args_command_list, args_target_list) = self._parser.parse_args()

        if args_command_list.update_trust_stores:
            # Just update the trust stores and do nothing
            TrustStoresRepository.update_default()
            raise TrustStoresUpdateCompleted()

        # Handle the --targets_in command line and fill args_target_list
        if args_command_list.targets_in:
            if args_target_list:
                raise CommandLineParsingError('Cannot use --targets_list and specify targets within the command line.')

            try:  # Read targets from a file
                with open(args_command_list.targets_in) as f:
                    for target in f.readlines():
                        if target.strip():  # Ignore empty lines
                            if not target.startswith('#'):  # Ignore comment lines
                                args_target_list.append(target.strip())
            except IOError:
                raise CommandLineParsingError('Can\'t read targets from input file \'{}.'.format(
                    args_command_list.targets_in))

        if not args_target_list:
            raise CommandLineParsingError('No targets to scan.')

        # Handle the --regular command line parameter as a shortcut
        if self._parser.has_option('--regular'):
            if getattr(args_command_list, 'regular'):
                setattr(args_command_list, 'regular', False)
                for cmd in self.REGULAR_CMD:
                    setattr(args_command_list, cmd, True)

        # Sanity checks on the command line options
        # Prevent --quiet and --xml_out -
        if args_command_list.xml_file and args_command_list.xml_file == '-' and args_command_list.quiet:
            raise CommandLineParsingError('Cannot use --quiet with --xml_out -.')

        # Prevent --quiet and --json_out -
        if args_command_list.json_file and args_command_list.json_file == '-' and args_command_list.quiet:
            raise CommandLineParsingError('Cannot use --quiet with --json_out -.')

        # Prevent --xml_out - and --json_out -
        if args_command_list.json_file and args_command_list.json_file == '-' \
                and args_command_list.xml_file and args_command_list.xml_file == '-':
            raise CommandLineParsingError('Cannot use --xml_out - with --json_out -.')

        # Sanity checks on the client cert options
        client_auth_creds = None
        if bool(args_command_list.cert) ^ bool(args_command_list.key):
            raise CommandLineParsingError('No private key or certificate file were given. See --cert and --key.')

        elif args_command_list.cert:
            # Private key formats
            if args_command_list.keyform == 'DER':
                key_type = OpenSslFileTypeEnum.ASN1
            elif args_command_list.keyform == 'PEM':
                key_type = OpenSslFileTypeEnum.PEM
            else:
                raise CommandLineParsingError('--keyform should be DER or PEM.')

            # Let's try to open the cert and key files
            try:
                client_auth_creds = ClientAuthenticationCredentials(args_command_list.cert,
                                                                    args_command_list.key,
                                                                    key_type,
                                                                    args_command_list.keypass)
            except ValueError as e:
                raise CommandLineParsingError('Invalid client authentication settings: {}.'.format(e.args[0]))

        # HTTP CONNECT proxy
        http_tunneling_settings = None
        if args_command_list.https_tunnel:
            try:
                http_tunneling_settings = HttpConnectTunnelingSettings.from_url(args_command_list.https_tunnel)
            except ValueError as e:
                raise CommandLineParsingError('Invalid proxy URL for --https_tunnel: {}.'.format(e.args[0]))

        # STARTTLS
        tls_wrapped_protocol = TlsWrappedProtocolEnum.PLAIN_TLS
        if args_command_list.starttls:
            if args_command_list.starttls not in self.START_TLS_PROTOCOLS:
                raise CommandLineParsingError(self.START_TLS_USAGE)
            else:
                # StartTLS was specified
                if args_command_list.starttls in self.STARTTLS_PROTOCOL_DICT.keys():
                    # Protocol was given in the command line
                    tls_wrapped_protocol = self.STARTTLS_PROTOCOL_DICT[args_command_list.starttls]

        # Create the server connectivity tester for each specified servers
        # A limitation when using the command line is that only one client_auth_credentials and http_tunneling_settings
        # can be specified, for all the servers to scan
        good_server_list = []
        bad_server_list = []
        for server_string in args_target_list:
            try:
                hostname, ip_address, port = CommandLineServerStringParser.parse_server_string(server_string)
            except ServerStringParsingError as e:
                # Will happen if the server string is malformed
                bad_server_list.append(e)
                continue

            try:
                # TODO(AD): Unicode hostnames may fail on Python2
                # hostname = hostname.decode('utf-8')
                server_info = ServerConnectivityTester(
                    hostname=hostname,
                    port=port,
                    ip_address=ip_address,
                    tls_wrapped_protocol=tls_wrapped_protocol,
                    tls_server_name_indication=args_command_list.sni,
                    xmpp_to_hostname=args_command_list.xmpp_to,
                    client_auth_credentials=client_auth_creds,
                    http_tunneling_settings=http_tunneling_settings
                )
                good_server_list.append(server_info)
            except ValueError as e:
                # Will happen for example if xmpp_to is specified for a non-XMPP connection
                raise CommandLineParsingError(e.args[0])

        # Command line hacks
        # Handle --starttls=auto now that we parsed the server strings
        if args_command_list.starttls == 'auto':
            for server_info in good_server_list:
                # We use the port number to deduce the protocol
                if server_info.port in self.STARTTLS_PROTOCOL_DICT.keys():
                    server_info.tls_wrapped_protocol = self.STARTTLS_PROTOCOL_DICT[server_info.port]

        # Handle --http_get now that we parsed the server strings
        # Doing it here is hacky as the option is defined within PluginOpenSSLCipherSuites
        if args_command_list.http_get:
            for server_info in good_server_list:
                if server_info.port == 443:
                    server_info.tls_wrapped_protocol = TlsWrappedProtocolEnum.HTTPS

        return good_server_list, bad_server_list, args_command_list
 def test_update_default(self):
     repo = TrustStoresRepository.update_default()
     self.assertTrue(repo.get_main_store())
     self.assertEqual(len(repo.get_all_stores()), 5)
Example #16
0
    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
    def process_task(
            self,
            server_info: ServerConnectivityInfo,
            scan_command: PluginScanCommand
    ) -> 'CertificateInfoScanResult':
        if not isinstance(scan_command, CertificateInfoScanCommand):
            raise ValueError('Unexpected scan command')

        final_trust_store_list = TrustStoresRepository.get_default().get_all_stores()
        if scan_command.custom_ca_file:
            custom_ca_file_path = Path(scan_command.custom_ca_file)
            if not custom_ca_file_path.is_file():
                raise ValueError(f'Could not open supplied CA file at "{custom_ca_file_path}"')
            final_trust_store_list.append(TrustStore(custom_ca_file_path, 'Custom --ca_file', 'N/A'))

        # Workaround for https://github.com/pyca/cryptography/issues/3495
        default_backend()

        thread_pool = ThreadPool()
        for trust_store in final_trust_store_list:
            # Try to connect with each trust store
            thread_pool.add_job((self._get_and_verify_certificate_chain, [server_info, trust_store]))

        # Start processing the jobs; one thread per trust
        thread_pool.start(len(final_trust_store_list))

        # Store the results as they come
        path_validation_result_list = []
        path_validation_error_list = []
        ocsp_response = None
        received_chain = None

        for (job, result) in thread_pool.get_result():
            (_, (_, trust_store)) = job
            received_chain_as_pem, verified_chain_as_pem, validation_result, _ocsp_response = result

            # Parse the certificates using the cryptography module
            if not received_chain:
                received_chain = [
                    load_pem_x509_certificate(pem_cert.encode('ascii'), backend=default_backend())
                    for pem_cert in received_chain_as_pem
                ]
            verified_chain = [
                load_pem_x509_certificate(cert_as_pem.encode('ascii'), backend=default_backend())
                for cert_as_pem in verified_chain_as_pem
            ] if verified_chain_as_pem else None

            # Keep the OCSP response if the validation was successful and a response was returned
            if _ocsp_response:
                ocsp_response = _ocsp_response

            # Store the returned verify string for each trust store
            path_validation_result_list.append(PathValidationResult(trust_store, verified_chain, validation_result))

        # Store thread pool errors
        last_exception = None
        for (job, exception) in thread_pool.get_error():
            (_, (_, trust_store)) = job
            path_validation_error_list.append(PathValidationError(trust_store, exception))
            last_exception = exception

        thread_pool.join()

        if len(path_validation_error_list) == len(final_trust_store_list):
            # All connections failed unexpectedly; raise an exception instead of returning a result
            raise last_exception  # type: ignore

        if not received_chain:
            raise ValueError('Error: Could not retrieve the server certificate chain')

        # All done
        return CertificateInfoScanResult(
            server_info,
            scan_command,
            received_chain,
            path_validation_result_list,
            path_validation_error_list,
            ocsp_response
        )
 def test_get_default(self):
     repo = TrustStoresRepository.get_default()
     assert repo.get_main_store()
     assert len(repo.get_all_stores()) == 5
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()
Example #20
0
    def process_task(
            self, server_info: ServerConnectivityInfo,
            scan_command: PluginScanCommand) -> "HttpHeadersScanResult":
        if not isinstance(scan_command, HttpHeadersScanCommand):
            raise ValueError("Unexpected scan command")

        if server_info.tls_wrapped_protocol not in [
                TlsWrappedProtocolEnum.PLAIN_TLS, TlsWrappedProtocolEnum.HTTPS
        ]:
            raise ValueError(
                "Cannot test for HTTP headers on a StartTLS connection.")

        # Perform the SSL handshake
        mozilla_store = TrustStoresRepository.get_default().get_main_store()
        ssl_connection = server_info.get_preconfigured_ssl_connection(
            ssl_verify_locations=mozilla_store.path)
        try:
            ssl_connection.connect()
            try:
                verified_chain_as_pem = ssl_connection.ssl_client.get_verified_chain(
                )
            except CouldNotBuildVerifiedChain:
                verified_chain_as_pem = None

            # Send an HTTP GET request to the server
            ssl_connection.ssl_client.write(
                HttpRequestGenerator.get_request(host=server_info.hostname))

            # We do not follow redirections because the security headers must be set on the first page according to
            # https://hstspreload.appspot.com/:
            # "If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS
            # header (rather than the page it redirects to)."
            http_response = HttpResponseParser.parse_from_ssl_connection(
                ssl_connection.ssl_client)
        finally:
            ssl_connection.close()

        if http_response.version == 9:
            # HTTP 0.9 => Probably not an HTTP response
            raise ValueError("Server did not return an HTTP response")

        # Parse the certificate chain
        verified_chain = ([
            load_pem_x509_certificate(cert_as_pem.encode("ascii"),
                                      backend=default_backend())
            for cert_as_pem in verified_chain_as_pem
        ] if verified_chain_as_pem else None)

        # Parse each header
        hsts_header = StrictTransportSecurityHeader.from_http_response(
            http_response)
        expect_ct_header = ExpectCtHeader.from_http_response(http_response)
        hpkp_header = PublicKeyPinsHeader.from_http_response(http_response)
        hpkp_report_only_header = PublicKeyPinsReportOnlyHeader.from_http_response(
            http_response)

        return HttpHeadersScanResult(
            server_info,
            scan_command,
            hsts_header,
            hpkp_header,
            hpkp_report_only_header,
            expect_ct_header,
            verified_chain,
        )
Example #21
0
    def parse_command_line(
        self
    ) -> Tuple[List[ServerConnectivityTester], List[ServerStringParsingError],
               Any]:
        """Parses the command line used to launch SSLyze.
        """
        (args_command_list, args_target_list) = self._parser.parse_args()

        if args_command_list.update_trust_stores:
            # Just update the trust stores and do nothing
            TrustStoresRepository.update_default()
            raise TrustStoresUpdateCompleted()

        # Handle the --targets_in command line and fill args_target_list
        if args_command_list.targets_in:
            if args_target_list:
                raise CommandLineParsingError(
                    "Cannot use --targets_list and specify targets within the command line."
                )

            try:  # Read targets from a file
                with open(args_command_list.targets_in) as f:
                    for target in f.readlines():
                        if target.strip():  # Ignore empty lines
                            if not target.startswith(
                                    "#"):  # Ignore comment lines
                                args_target_list.append(target.strip())
            except IOError:
                raise CommandLineParsingError(
                    "Can't read targets from input file '{}.".format(
                        args_command_list.targets_in))

        if not args_target_list:
            raise CommandLineParsingError("No targets to scan.")

        # Handle the --regular command line parameter as a shortcut
        if self._parser.has_option("--regular"):
            if getattr(args_command_list, "regular"):
                setattr(args_command_list, "regular", False)
                for cmd in self.REGULAR_CMD:
                    setattr(args_command_list, cmd, True)

        # Sanity checks on the command line options
        # Prevent --quiet and --xml_out -
        if args_command_list.xml_file and args_command_list.xml_file == "-" and args_command_list.quiet:
            raise CommandLineParsingError(
                "Cannot use --quiet with --xml_out -.")

        # Prevent --quiet and --json_out -
        if args_command_list.json_file and args_command_list.json_file == "-" and args_command_list.quiet:
            raise CommandLineParsingError(
                "Cannot use --quiet with --json_out -.")

        # Prevent --xml_out - and --json_out -
        if (args_command_list.json_file and args_command_list.json_file == "-"
                and args_command_list.xml_file
                and args_command_list.xml_file == "-"):
            raise CommandLineParsingError(
                "Cannot use --xml_out - with --json_out -.")

        # Sanity checks on the client cert options
        client_auth_creds = None
        if bool(args_command_list.cert) ^ bool(args_command_list.key):
            raise CommandLineParsingError(
                "No private key or certificate file were given. See --cert and --key."
            )

        elif args_command_list.cert:
            # Private key formats
            if args_command_list.keyform == "DER":
                key_type = OpenSslFileTypeEnum.ASN1
            elif args_command_list.keyform == "PEM":
                key_type = OpenSslFileTypeEnum.PEM
            else:
                raise CommandLineParsingError(
                    "--keyform should be DER or PEM.")

            # Let's try to open the cert and key files
            try:
                client_auth_creds = ClientAuthenticationCredentials(
                    args_command_list.cert, args_command_list.key, key_type,
                    args_command_list.keypass)
            except ValueError as e:
                raise CommandLineParsingError(
                    "Invalid client authentication settings: {}.".format(
                        e.args[0]))

        # HTTP CONNECT proxy
        http_tunneling_settings = None
        if args_command_list.https_tunnel:
            try:
                http_tunneling_settings = HttpConnectTunnelingSettings.from_url(
                    args_command_list.https_tunnel)
            except ValueError as e:
                raise CommandLineParsingError(
                    "Invalid proxy URL for --https_tunnel: {}.".format(
                        e.args[0]))

        # STARTTLS
        tls_wrapped_protocol = TlsWrappedProtocolEnum.PLAIN_TLS
        if args_command_list.starttls:
            if args_command_list.starttls not in self.START_TLS_PROTOCOLS:
                raise CommandLineParsingError(self.START_TLS_USAGE)
            else:
                # StartTLS was specified
                if args_command_list.starttls in self.STARTTLS_PROTOCOL_DICT.keys(
                ):
                    # Protocol was given in the command line
                    tls_wrapped_protocol = self.STARTTLS_PROTOCOL_DICT[
                        args_command_list.starttls]

        # Create the server connectivity tester for each specified servers
        # A limitation when using the command line is that only one client_auth_credentials and http_tunneling_settings
        # can be specified, for all the servers to scan
        good_server_list = []
        bad_server_list = []
        for server_string in args_target_list:
            try:
                hostname, ip_address, port = CommandLineServerStringParser.parse_server_string(
                    server_string)
            except ServerStringParsingError as e:
                # Will happen if the server string is malformed
                bad_server_list.append(e)
                continue

            try:
                # TODO(AD): Unicode hostnames may fail on Python2
                # hostname = hostname.decode('utf-8')
                server_info = ServerConnectivityTester(
                    hostname=hostname,
                    port=port,
                    ip_address=ip_address,
                    tls_wrapped_protocol=tls_wrapped_protocol,
                    tls_server_name_indication=args_command_list.sni,
                    xmpp_to_hostname=args_command_list.xmpp_to,
                    client_auth_credentials=client_auth_creds,
                    http_tunneling_settings=http_tunneling_settings,
                )
                good_server_list.append(server_info)
            except ValueError as e:
                # Will happen for example if xmpp_to is specified for a non-XMPP connection
                raise CommandLineParsingError(e.args[0])

        # Command line hacks
        # Handle --starttls=auto now that we parsed the server strings
        if args_command_list.starttls == "auto":
            for server_info in good_server_list:
                # We use the port number to deduce the protocol
                if server_info.port in self.STARTTLS_PROTOCOL_DICT.keys():
                    server_info.tls_wrapped_protocol = self.STARTTLS_PROTOCOL_DICT[
                        server_info.port]

        # Handle --http_get now that we parsed the server strings
        # Doing it here is hacky as the option is defined within PluginOpenSSLCipherSuites
        if args_command_list.http_get:
            for server_info in good_server_list:
                if server_info.port == 443:
                    server_info.tls_wrapped_protocol = TlsWrappedProtocolEnum.HTTPS

        return good_server_list, bad_server_list, args_command_list
Example #22
0
    def as_xml(self):
        xml_output = Element(self.scan_command.get_cli_argument(),
                             title=self.scan_command.get_title())

        # Certificate chain
        cert_chain_attrs = {
            'isChainOrderValid':
            str(self.is_certificate_chain_order_valid),
            'suppliedServerNameIndication':
            self.server_info.tls_server_name_indication,
            'containsAnchorCertificate':
            str(False)
            if not self.has_anchor_in_certificate_chain else str(True),
            'hasMustStapleExtension':
            str(self.certificate_has_must_staple_extension),
            'includedSctsCount':
            str(self.certificate_included_scts_count),
        }
        cert_chain_xml = Element('receivedCertificateChain',
                                 attrib=cert_chain_attrs)
        for cert_xml in self._certificate_chain_to_xml(self.certificate_chain):
            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: EV certs
            if self.is_leaf_certificate_ev and TrustStoresRepository.get_main(
            ) == path_result.trust_store:
                path_attrib_xml['isExtendedValidationCertificate'] = str(
                    self.is_leaf_certificate_ev)

            path_valid_xml = Element('pathValidation', attrib=path_attrib_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))

        # Verified chain
        if self.verified_certificate_chain:
            verified_cert_chain_xml = Element(
                'verifiedCertificateChain', {
                    'hasSha1SignedCertificate':
                    str(self.has_sha1_in_certificate_chain),
                    'suppliedServerNameIndication':
                    self.server_info.tls_server_name_indication,
                    'successfulTrustStore':
                    self.successful_trust_store.name,
                    'hasMustStapleExtension':
                    str(self.certificate_has_must_staple_extension),
                    'includedSctsCount':
                    str(self.certificate_included_scts_count),
                })
            for cert_xml in self._certificate_chain_to_xml(
                    self.verified_certificate_chain):
                verified_cert_chain_xml.append(cert_xml)
            trust_validation_xml.append(verified_cert_chain_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:
            if self.ocsp_response_status != OcspResponseStatusEnum.SUCCESSFUL:
                ocsp_resp_xmp = Element(
                    'ocspResponse',
                    attrib={'status': self.ocsp_response_status.name})
            else:
                ocsp_resp_xmp = Element('ocspResponse',
                                        attrib={
                                            'isTrustedByMozillaCAStore':
                                            str(self.is_ocsp_response_trusted),
                                            'status':
                                            self.ocsp_response_status.name
                                        })

                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)

            ocsp_xml.append(ocsp_resp_xmp)
        xml_output.append(ocsp_xml)

        # All done
        return xml_output
Example #23
0
 def test_update_default(self):
     repo = TrustStoresRepository.update_default()
     assert repo.get_main_store()
     assert len(repo.get_all_stores()) == 5
Example #24
0
 def test_get_default(self):
     repo = TrustStoresRepository.get_default()
     self.assertTrue(repo.get_main_store())
     self.assertGreater(len(repo.get_all_stores()), 5)
Example #25
0
    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 test_get_default(self):
     repo = TrustStoresRepository.get_default()
     self.assertTrue(repo.get_main_store())
     self.assertEqual(len(repo.get_all_stores()), 5)
Example #27
0
    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)
Example #28
0
 def test_update_default(self):
     repo = TrustStoresRepository.update_default()
     self.assertTrue(repo.get_main_store())
     self.assertGreater(len(repo.get_all_stores()), 5)