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
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)
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)