def create_pair_issuer_subject(self, cert_map):
        """Creates pairs of issuer and subject certificates."""
        issuer_subject = []
        for subject_der in cert_map:
            subject = cert_map[subject_der]
            if subject.ocsp_no_check_value or \
                    subject.ca and not subject.ocsp_urls:
                # Root certificate will not be validated
                # but it is used to validate the subject certificate
                continue
            issuer_hash = subject.issuer.sha256
            if issuer_hash not in cert_map:
                # IF NO ROOT certificate is attached in the certificate chain
                # read it from the local disk
                self._lazy_read_ca_bundle()
                logger.debug('not found issuer_der: %s', subject.issuer.native)
                if issuer_hash not in SnowflakeOCSP.ROOT_CERTIFICATES_DICT:
                    raise RevocationCheckError(
                        msg="CA certificate is NOT found in the root "
                            "certificate list. Make sure you use the latest "
                            "Python Connector package and the URL is valid.")
                issuer = SnowflakeOCSP.ROOT_CERTIFICATES_DICT[issuer_hash]
            else:
                issuer = cert_map[issuer_hash]

            issuer_subject.append((issuer, subject))
        return issuer_subject
    def verify_signature(self, signature_algorithm, signature, cert, data):
        use_openssl_only = os.getenv('SF_USE_OPENSSL_ONLY', 'False') == 'True'
        if not use_openssl_only:
            pubkey = asymmetric.load_public_key(cert.public_key).unwrap().dump()
            rsakey = RSA.importKey(pubkey)
            signer = PKCS1_v1_5.new(rsakey)
            if signature_algorithm in SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS:
                digest = \
                    SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS[
                    signature_algorithm].new()
            else:
                # the last resort. should not happen.
                digest = SHA1.new()
            digest.update(data.dump())
            if not signer.verify(digest, signature):
                raise RevocationCheckError(
                    msg="Failed to verify the signature",
                    errno=ER_INVALID_OCSP_RESPONSE)

        else:
            backend = default_backend()
            public_key = serialization.load_der_public_key(cert.public_key.dump(), backend=default_backend())
            if signature_algorithm in SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS:
                chosen_hash = \
                    SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS_OPENSSL[
                        signature_algorithm]()
            else:
                # the last resort. should not happen.
                chosen_hash = hashes.SHA1()
            hasher = hashes.Hash(chosen_hash, backend)
            hasher.update(data.dump())
            digest = hasher.finalize()
            try:
                public_key.verify(
                    signature,
                    digest,
                    padding.PKCS1v15(),
                    utils.Prehashed(chosen_hash)
                )
            except InvalidSignature:
                raise RevocationCheckError(
                    msg="Failed to verify the signature",
                    errno=ER_INVALID_OCSP_RESPONSE)
    def is_valid_time(self, cert_id, ocsp_response):
        res = OCSPResponse.load(ocsp_response)

        if res['response_status'].native != 'successful':
            raise RevocationCheckError(msg="Invalid Status: {0}".format(
                res['response_status'].native),
                                       errno=ER_INVALID_OCSP_RESPONSE)

        basic_ocsp_response = res.basic_ocsp_response
        if basic_ocsp_response['certs'].native:
            logger.debug("Certificate is attached in Basic OCSP Response")
            ocsp_cert = basic_ocsp_response['certs'][0]
            logger.debug("Verifying the attached certificate is signed by "
                         "the issuer")
            logger.debug(
                "Valid Not After: %s",
                ocsp_cert['tbs_certificate']['validity']['not_after'].native)

            cur_time = datetime.now(timezone.utc)
            """
            Note:
            We purposefully do not verify certificate signature here.
            The OCSP Response is extracted from the OCSP Response Cache
            which is expected to have OCSP Responses with verified
            attached signature. Moreover this OCSP Response is eventually
            going to be processed by the driver before being consumed by
            the driver.
            This step ensures that the OCSP Response cache does not have
            any invalid entries.
            """
            cert_valid, debug_msg = self.check_cert_time_validity(
                cur_time, ocsp_cert)
            if not cert_valid:
                logger.debug(debug_msg)
                return False

        tbs_response_data = basic_ocsp_response['tbs_response_data']

        single_response = tbs_response_data['responses'][0]
        cert_status = single_response['cert_status'].name

        try:
            if cert_status == 'good':
                self._process_good_status(single_response, cert_id,
                                          ocsp_response)
        except Exception as ex:
            logger.debug("Failed to validate ocsp response %s", ex)
            return False

        return True
예제 #4
0
 def verify_signature(self, signature_algorithm, signature, cert, data):
     pubkey = asymmetric.load_public_key(cert.public_key).unwrap().dump()
     rsakey = RSA.importKey(pubkey)
     signer = PKCS1_v1_5.new(rsakey)
     if signature_algorithm in SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS:
         digest = \
             SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS[
                 signature_algorithm].new()
     else:
         # the last resort. should not happen.
         digest = SHA1.new()
     digest.update(data.dump())
     if not signer.verify(digest, signature):
         raise RevocationCheckError(msg="Failed to verify the signature",
                                    errno=ER_INVALID_OCSP_RESPONSE)
    def is_valid_time(self, cert_id, ocsp_response):
        res = OCSPResponse.load(ocsp_response)

        if res['response_status'].native != 'successful':
            raise RevocationCheckError(msg="Invalid Status: {0}".format(
                res['response_status'].native),
                                       errno=ER_INVALID_OCSP_RESPONSE)

        basic_ocsp_response = res.basic_ocsp_response
        tbs_response_data = basic_ocsp_response['tbs_response_data']

        single_response = tbs_response_data['responses'][0]
        cert_status = single_response['cert_status'].name

        try:
            if cert_status == 'good':
                self._process_good_status(single_response, cert_id,
                                          ocsp_response)
        except Exception as ex:
            logger.debug("Failed to validate ocsp response %s", ex)
            return False

        return True
import pytest
from snowflake.connector.errorcode import ER_FAILED_TO_REQUEST
from snowflake.connector.errors import RevocationCheckError
from snowflake.connector.sqlstate import SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED
from snowflake.connector.telemetry_oob import TelemetryService

DEV_CONFIG = {
    'host': 'localhost',
    'port': 8080,
    'account': 'testAccount',
    'user': '******',
    'password': '******',
    'protocol': 'http'
}
telemetry_data = {}
exception = RevocationCheckError("Test OCSP Revocation error")
event_type = "Test OCSP Exception"
stack_trace = [
    'Traceback (most recent call last):\n',
    '  File "<doctest...>", line 10, in <module>\n    lumberjack()\n',
    '  File "<doctest...>", line 4, in lumberjack\n    bright_side_of_death()\n',
    '  File "<doctest...>", line 7, in bright_side_of_death\n    return tuple()[0]\n',
    'IndexError: tuple index out of range\n'
]

event_name = "HttpRetryTimeout"
url = "http://localhost:8080/queries/v1/query-request?request_guid=a54a3d70-abf2-4576-bb6f-ddf23999491a"
method = "POST"


@pytest.fixture()
    def process_ocsp_response(self, issuer, cert_id, ocsp_response):
        try:
            res = OCSPResponse.load(ocsp_response)
            if self.test_mode is not None:
                ocsp_load_failure = getenv("SF_TEST_OCSP_FORCE_BAD_OCSP_RESPONSE")
                if ocsp_load_failure is not None:
                    raise RevocationCheckError("Force fail")
        except Exception:
            raise RevocationCheckError(
                msg='Invalid OCSP Response',
                errno=ER_INVALID_OCSP_RESPONSE
            )

        if res['response_status'].native != 'successful':
            raise RevocationCheckError(
                msg="Invalid Status: {}".format(res['response_status'].native),
                errno=ER_INVALID_OCSP_RESPONSE)

        basic_ocsp_response = res.basic_ocsp_response
        if basic_ocsp_response['certs'].native:
            logger.debug("Certificate is attached in Basic OCSP Response")
            ocsp_cert = basic_ocsp_response['certs'][0]
            logger.debug("Verifying the attached certificate is signed by "
                         "the issuer")
            logger.debug(
                "Valid Not After: %s",
                 ocsp_cert['tbs_certificate']['validity']['not_after'].native)

            cur_time = datetime.now(timezone.utc)

            """
            Signature verification should happen before any kind of
            validation
            """
            self.verify_signature(
                ocsp_cert.hash_algo,
                ocsp_cert.signature,
                issuer,
                ocsp_cert['tbs_certificate'])

            cert_valid, debug_msg = self.check_cert_time_validity(cur_time, ocsp_cert)

            if not cert_valid:
                raise RevocationCheckError(
                    msg=debug_msg,
                    errno=ER_INVALID_OCSP_RESPONSE_CODE)

        else:
            logger.debug("Certificate is NOT attached in Basic OCSP Response. "
                         "Using issuer's certificate")
            ocsp_cert = issuer

        tbs_response_data = basic_ocsp_response['tbs_response_data']

        logger.debug("Verifying the OCSP response is signed by the issuer.")
        self.verify_signature(
            basic_ocsp_response['signature_algorithm'].hash_algo,
            basic_ocsp_response['signature'].native,
            ocsp_cert,
            tbs_response_data)

        single_response = tbs_response_data['responses'][0]
        cert_status = single_response['cert_status'].name
        if self.test_mode is not None:
            test_cert_status = getenv("SF_TEST_OCSP_CERT_STATUS")
            if test_cert_status == 'revoked':
                cert_status = 'revoked'
            elif test_cert_status == 'unknown':
                cert_status = 'unknown'
            elif test_cert_status == 'good':
                cert_status = 'good'

        try:
            if cert_status == 'good':
                self._process_good_status(single_response, cert_id, ocsp_response)
                SnowflakeOCSP.OCSP_CACHE.update_cache(self, cert_id, ocsp_response)
            elif cert_status == 'revoked':
                self._process_revoked_status(single_response, cert_id)
            elif cert_status == 'unknown':
                self._process_unknown_status(cert_id)
            else:
                debug_msg = "Unknown revocation status was returned." \
                            "OCSP response may be malformed: {}.".\
                    format(cert_status)
                raise RevocationCheckError(
                    msg=debug_msg,
                    errno=ER_INVALID_OCSP_RESPONSE_CODE
                )
        except RevocationCheckError as op_er:
            debug_msg = "{} Consider running curl -o ocsp.der {}".\
                format(op_er.msg,
                           self.debug_ocsp_failure_url)
            raise RevocationCheckError(msg=debug_msg, errno=op_er.errno)
    def process_ocsp_response(self, issuer, cert_id, ocsp_response):
        try:
            res = OCSPResponse.load(ocsp_response)
        except Exception:
            raise RevocationCheckError(msg='Invalid OCSP Response',
                                       errno=ER_INVALID_OCSP_RESPONSE)
        if res['response_status'].native != 'successful':
            raise RevocationCheckError(msg="Invalid Status: {0}".format(
                res['response_status'].native),
                                       errno=ER_INVALID_OCSP_RESPONSE)

        basic_ocsp_response = res.basic_ocsp_response
        if basic_ocsp_response['certs'].native:
            logger.debug("Certificate is attached in Basic OCSP Response")
            ocsp_cert = basic_ocsp_response['certs'][0]
            logger.debug("Verifying the attached certificate is signed by "
                         "the issuer")
            logger.debug(
                "Valid Not After: %s",
                ocsp_cert['tbs_certificate']['validity']['not_after'].native)

            cur_time = datetime.now(timezone.utc)

            if cur_time > ocsp_cert['tbs_certificate']['validity']['not_after'].native or \
                    cur_time < ocsp_cert['tbs_certificate']['validity']['not_before'].native:
                debug_msg = "Certificate attached to OCSP response is invalid. OCSP response "\
                            "current time - {0} certificate not before time - {1} certificate "\
                            "not after time - {2}. Consider running curl -o ocsp.der {3}".\
                    format(cur_time,
                           ocsp_cert['tbs_certificate']['validity']['not_before'].native,
                           ocsp_cert['tbs_certificate']['validity']['not_after'].native,
                           super(SnowflakeOCSPAsn1Crypto, self).debug_ocsp_failure_url)

                raise RevocationCheckError(msg=debug_msg,
                                           errno=ER_INVALID_OCSP_RESPONSE_CODE)

            self.verify_signature(ocsp_cert.hash_algo, ocsp_cert.signature,
                                  issuer, ocsp_cert['tbs_certificate'])
        else:
            logger.debug("Certificate is NOT attached in Basic OCSP Response. "
                         "Using issuer's certificate")
            ocsp_cert = issuer

        tbs_response_data = basic_ocsp_response['tbs_response_data']

        logger.debug("Verifying the OCSP response is signed by the issuer.")
        self.verify_signature(
            basic_ocsp_response['signature_algorithm'].hash_algo,
            basic_ocsp_response['signature'].native, ocsp_cert,
            tbs_response_data)

        single_response = tbs_response_data['responses'][0]
        cert_status = single_response['cert_status'].name
        try:
            if cert_status == 'good':
                self._process_good_status(single_response, cert_id,
                                          ocsp_response)
                SnowflakeOCSP.OCSP_CACHE.update_cache(self, cert_id,
                                                      ocsp_response)
            elif cert_status == 'revoked':
                self._process_revoked_status(single_response, cert_id)
            elif cert_status == 'unknown':
                self._process_unknown_status(cert_id)
            else:
                debug_msg = "Unknown revocation status was returned." \
                            "OCSP response may be malformed: {0}.".\
                    format(cert_status)
                raise RevocationCheckError(msg=debug_msg,
                                           errno=ER_INVALID_OCSP_RESPONSE_CODE)
        except RevocationCheckError as op_er:
            debug_msg = "{0} Consider running curl -o ocsp.der {1}".\
                format(op_er.msg,
                           self.debug_ocsp_failure_url)
            raise RevocationCheckError(msg=debug_msg, errno=op_er.errno)
예제 #9
0
    def process_ocsp_response(self, issuer, cert_id, ocsp_response):
        try:
            res = OCSPResponse.load(ocsp_response)
            if self.test_mode is not None:
                ocsp_load_failure = getenv(
                    "SF_TEST_OCSP_FORCE_BAD_OCSP_RESPONSE")
                if ocsp_load_failure is not None:
                    raise RevocationCheckError(
                        "Force fail", errno=ER_OCSP_RESPONSE_LOAD_FAILURE)
        except Exception:
            raise RevocationCheckError(msg="Invalid OCSP Response",
                                       errno=ER_OCSP_RESPONSE_LOAD_FAILURE)

        if res["response_status"].native != "successful":
            raise RevocationCheckError(
                msg="Invalid Status: {}".format(res["response_status"].native),
                errno=ER_OCSP_RESPONSE_STATUS_UNSUCCESSFUL,
            )

        basic_ocsp_response = res.basic_ocsp_response
        if basic_ocsp_response["certs"].native:
            logger.debug("Certificate is attached in Basic OCSP Response")
            ocsp_cert = basic_ocsp_response["certs"][0]
            logger.debug("Verifying the attached certificate is signed by "
                         "the issuer")
            logger.debug(
                "Valid Not After: %s",
                ocsp_cert["tbs_certificate"]["validity"]["not_after"].native,
            )

            cur_time = datetime.now(timezone.utc)

            try:
                """
                Signature verification should happen before any kind of
                validation
                """
                self.verify_signature(
                    ocsp_cert.hash_algo,
                    ocsp_cert.signature,
                    issuer,
                    ocsp_cert["tbs_certificate"],
                )
            except RevocationCheckError as rce:
                raise RevocationCheckError(
                    msg=rce.msg, errno=ER_OCSP_RESPONSE_ATTACHED_CERT_INVALID)
            cert_valid, debug_msg = self.check_cert_time_validity(
                cur_time, ocsp_cert)

            if not cert_valid:
                raise RevocationCheckError(
                    msg=debug_msg,
                    errno=ER_OCSP_RESPONSE_ATTACHED_CERT_EXPIRED)

        else:
            logger.debug("Certificate is NOT attached in Basic OCSP Response. "
                         "Using issuer's certificate")
            ocsp_cert = issuer

        tbs_response_data = basic_ocsp_response["tbs_response_data"]

        logger.debug("Verifying the OCSP response is signed by the issuer.")
        try:
            self.verify_signature(
                basic_ocsp_response["signature_algorithm"].hash_algo,
                basic_ocsp_response["signature"].native,
                ocsp_cert,
                tbs_response_data,
            )
        except RevocationCheckError as rce:
            raise RevocationCheckError(
                msg=rce.msg, errno=ER_OCSP_RESPONSE_INVALID_SIGNATURE)

        single_response = tbs_response_data["responses"][0]
        cert_status = single_response["cert_status"].name
        if self.test_mode is not None:
            test_cert_status = getenv("SF_TEST_OCSP_CERT_STATUS")
            if test_cert_status == "revoked":
                cert_status = "revoked"
            elif test_cert_status == "unknown":
                cert_status = "unknown"
            elif test_cert_status == "good":
                cert_status = "good"

        try:
            if cert_status == "good":
                self._process_good_status(single_response, cert_id,
                                          ocsp_response)
                SnowflakeOCSP.OCSP_CACHE.update_cache(self, cert_id,
                                                      ocsp_response)
            elif cert_status == "revoked":
                self._process_revoked_status(single_response, cert_id)
            elif cert_status == "unknown":
                self._process_unknown_status(cert_id)
            else:
                debug_msg = (
                    "Unknown revocation status was returned."
                    "OCSP response may be malformed: {}.".format(cert_status))
                raise RevocationCheckError(
                    msg=debug_msg, errno=ER_OCSP_RESPONSE_CERT_STATUS_INVALID)
        except RevocationCheckError as op_er:
            debug_msg = "{} Consider running curl -o ocsp.der {}".format(
                op_er.msg, self.debug_ocsp_failure_url)
            raise RevocationCheckError(msg=debug_msg, errno=op_er.errno)