def test_ocsp_single_endpoint(): environ["SF_OCSP_ACTIVATE_NEW_ENDPOINT"] = "True" SnowflakeOCSP.clear_cache() ocsp = SFOCSP() ocsp.OCSP_CACHE_SERVER.NEW_DEFAULT_CACHE_SERVER_BASE_URL = "https://snowflake.preprod3.us-west-2-dev.external-zone.snowflakecomputing.com:8085/ocsp/" connection = _openssl_connect("snowflake.okta.com") assert ocsp.validate( "snowflake.okta.com", connection), "Failed to validate: {}".format("snowflake.okta.com") del environ["SF_OCSP_ACTIVATE_NEW_ENDPOINT"]
def test_ocsp_incomplete_chain(): """Tests incomplete chained certificate.""" incomplete_chain_cert = path.join(THIS_DIR, '../data', 'cert_tests', 'incomplete-chain.pem') SnowflakeOCSP.clear_cache() # reset the memory cache ocsp = SFOCSP() with pytest.raises(OperationalError) as ex: ocsp.validate_certfile(incomplete_chain_cert) assert 'CA certificate is NOT found' in ex.value.msg
def test_ocsp_revoked_certificate(): """Tests revoked certificate.""" revoked_cert = path.join(THIS_DIR, '../data', 'cert_tests', 'revoked_certs.pem') SnowflakeOCSP.clear_cache() # reset the memory cache ocsp = SFOCSP() with pytest.raises(OperationalError) as ex: ocsp.validate_certfile(revoked_cert) assert ex.value.errno == ex.value.errno == ER_OCSP_RESPONSE_CERT_STATUS_REVOKED
def test_ocsp_with_file_cache(tmpdir): """OCSP tests and the cache server and file.""" tmp_dir = str(tmpdir.mkdir("ocsp_response_cache")) cache_file_name = path.join(tmp_dir, "cache_file.txt") # reset the memory cache SnowflakeOCSP.clear_cache() ocsp = SFOCSP(ocsp_response_cache_uri="file://" + cache_file_name) for url in TARGET_HOSTS: connection = _openssl_connect(url) assert ocsp.validate(url, connection), f"Failed to validate: {url}"
def test_ocsp(): """ OCSP tests """ # reset the memory cache SnowflakeOCSP.clear_cache() ocsp = SFOCSP() for url in TARGET_HOSTS: connection = _openssl_connect(url) assert ocsp.validate(url, connection), \ 'Failed to validate: {}'.format(url)
def dump_ocsp_response(urls, output_filename): ocsp = SFOCSP() for url in urls: if not url.startswith('http'): url = 'https://' + url parsed_url = urlsplit(url) hostname = parsed_url.hostname port = parsed_url.port or 443 connection = _openssl_connect(hostname, port) cert_data = ocsp.extract_certificate_chain(connection) current_time = int(time.time()) print("Target URL: {0}".format(url)) print("Current Time: {0}".format( strftime('%Y%m%d%H%M%SZ', gmtime(current_time)))) for issuer, subject in cert_data: cert_id, _ = ocsp.create_ocsp_request(issuer, subject) _, _, _, cert_id, ocsp_response_der = \ ocsp.validate_by_direct_connection(issuer, subject) ocsp_response = asn1crypto_ocsp.OCSPResponse.load(ocsp_response_der) print( "------------------------------------------------------------") print("Subject Name: {0}".format(subject.subject.native)) print("Issuer Name: {0}".format(issuer.subject.native)) print("OCSP URI: {0}".format(subject.ocsp_urls)) print("CRL URI: {0}".format( subject.crl_distribution_points[0].native)) print("Issuer Name Hash: {0}".format(subject.issuer.sha1)) print("Issuer Key Hash: {0}".format(issuer.public_key.sha1)) print("Serial Number: {0}".format(subject.serial_number)) print("Response Status: {0}".format( ocsp_response['response_status'].native)) basic_ocsp_response = ocsp_response.basic_ocsp_response tbs_response_data = basic_ocsp_response['tbs_response_data'] print("Responder ID: {0}".format( tbs_response_data['responder_id'].name)) current_time = int(time.time()) for single_response in tbs_response_data['responses']: cert_status = single_response['cert_status'].name if cert_status == 'good': dump_good_status(current_time, single_response) elif cert_status == 'revoked': dump_revoked_status(single_response) else: print("Unknown") print('') if output_filename: SFOCSP.OCSP_CACHE.write_ocsp_response_cache_file( ocsp, output_filename) return SFOCSP.OCSP_CACHE.CACHE
def test_ocsp_bad_validity(): SnowflakeOCSP.clear_cache() environ["SF_OCSP_TEST_MODE"] = "true" environ["SF_TEST_OCSP_FORCE_BAD_RESPONSE_VALIDITY"] = "true" OCSPCache.del_cache_file() ocsp = SFOCSP(use_ocsp_cache_server=False) connection = _openssl_connect("snowflake.okta.com") assert ocsp.validate("snowflake.okta.com", connection), "Connection should have passed with fail open" del environ['SF_OCSP_TEST_MODE'] del environ['SF_TEST_OCSP_FORCE_BAD_RESPONSE_VALIDITY']
def _validate_certs_using_ocsp(url, cache_file_name): """Validate OCSP response. Deleting memory cache and file cache randomly.""" logger = logging.getLogger('test') import time import random time.sleep(random.randint(0, 3)) if random.random() < 0.2: logger.info('clearing up cache: OCSP_VALIDATION_CACHE') SnowflakeOCSP.clear_cache() if random.random() < 0.05: logger.info('deleting a cache file: %s', cache_file_name) SnowflakeOCSP.delete_cache_file() connection = _openssl_connect(url) ocsp = SFOCSP(ocsp_response_cache_uri='file://' + cache_file_name) ocsp.validate(url, connection)
def _store_cache_in_file(tmpdir, target_hosts=None, filename=None): if target_hosts is None: target_hosts = TARGET_HOSTS if filename is None: filename = path.join(str(tmpdir), 'cache_file.txt') # cache OCSP response SnowflakeOCSP.clear_cache() ocsp = SFOCSP(ocsp_response_cache_uri='file://' + filename, use_ocsp_cache_server=False) for hostname in target_hosts: connection = _openssl_connect(hostname) assert ocsp.validate(hostname, connection), \ 'Failed to validate: {}'.format(hostname) assert path.exists(filename), "OCSP response cache file" return filename, target_hosts
def extract_certificate_file(input_filename): ocsp = SnowflakeOCSPAsn1Crypto() cert_map = {} ocsp.read_cert_bundle(input_filename, cert_map) for cert in cert_map.values(): print(f"serial #: {cert.serial_number}, name: {cert.subject.native}")
def _store_cache_in_file(tmpdir, target_hosts=None): if target_hosts is None: target_hosts = TARGET_HOSTS os.environ['SF_OCSP_RESPONSE_CACHE_DIR'] = str(tmpdir) OCSPCache.reset_cache_dir() filename = path.join(str(tmpdir), 'ocsp_response_cache.json') # cache OCSP response SnowflakeOCSP.clear_cache() ocsp = SFOCSP(ocsp_response_cache_uri='file://' + filename, use_ocsp_cache_server=False) for hostname in target_hosts: connection = _openssl_connect(hostname) assert ocsp.validate(hostname, connection), \ 'Failed to validate: {}'.format(hostname) assert path.exists(filename), "OCSP response cache file" return filename, target_hosts
def dump_good_status(current_time, single_response): print("This Update: {}".format(single_response['this_update'].native)) print("Next Update: {}".format(single_response['next_update'].native)) this_update = (single_response['this_update'].native.replace(tzinfo=None) - SFOCSP.ZERO_EPOCH).total_seconds() next_update = (single_response['next_update'].native.replace(tzinfo=None) - SFOCSP.ZERO_EPOCH).total_seconds() tolerable_validity = SFOCSP._calculate_tolerable_validity( this_update, next_update) print("Tolerable Update: {}".format( strftime('%Y%m%d%H%M%SZ', gmtime(next_update + tolerable_validity)))) if SFOCSP._is_validaity_range(current_time, this_update, next_update): print("OK") else: print( SFOCSP._validity_error_message(current_time, this_update, next_update))
def test_ocsp_fail_open_w_single_endpoint(): SnowflakeOCSP.clear_cache() OCSPCache.del_cache_file() environ["SF_OCSP_TEST_MODE"] = "true" environ["SF_TEST_OCSP_URL"] = "http://httpbin.org/delay/10" environ["SF_TEST_CA_OCSP_RESPONDER_CONNECTION_TIMEOUT"] = "5" ocsp = SFOCSP(use_ocsp_cache_server=False) connection = _openssl_connect("snowflake.okta.com") try: assert ocsp.validate("snowflake.okta.com", connection), \ 'Failed to validate: {}'.format("snowflake.okta.com") finally: del environ['SF_OCSP_TEST_MODE'] del environ['SF_TEST_OCSP_URL'] del environ['SF_TEST_CA_OCSP_RESPONDER_CONNECTION_TIMEOUT']
def test_ocsp_wo_cache_file(): """ OCSP tests without File cache. NOTE: Use /etc as a readonly directory such that no cache file is used. """ # reset the memory cache SnowflakeOCSP.clear_cache() OCSPCache.del_cache_file() environ['SF_OCSP_RESPONSE_CACHE_DIR'] = '/etc' OCSPCache.reset_cache_dir() try: ocsp = SFOCSP() for url in TARGET_HOSTS: connection = _openssl_connect(url) assert ocsp.validate(url, connection), \ 'Failed to validate: {}'.format(url) finally: del environ['SF_OCSP_RESPONSE_CACHE_DIR'] OCSPCache.reset_cache_dir()
def test_ocsp_fail_close_w_single_endpoint(): SnowflakeOCSP.clear_cache() environ["SF_OCSP_TEST_MODE"] = "true" environ["SF_TEST_OCSP_URL"] = "http://httpbin.org/delay/10" environ["SF_TEST_CA_OCSP_RESPONDER_CONNECTION_TIMEOUT"] = "5" OCSPCache.del_cache_file() ocsp = SFOCSP(use_ocsp_cache_server=False, use_fail_open=False) connection = _openssl_connect("snowflake.okta.com") with pytest.raises(RevocationCheckError) as ex: ocsp.validate("snowflake.okta.com", connection) try: assert ex.value.errno == ER_INVALID_OCSP_RESPONSE_CODE, "Connection should have failed" finally: del environ['SF_OCSP_TEST_MODE'] del environ['SF_TEST_OCSP_URL'] del environ['SF_TEST_CA_OCSP_RESPONDER_CONNECTION_TIMEOUT']
def dump_ocsp_response_cache(ocsp_response_cache_file, hostname_file, cert_glob_pattern): """Dump OCSP response cache contents. Show the subject name as well if the subject is included in the certificate files. """ sfocsp = SFOCSP() s_to_n = _fetch_certs(hostname_file) s_to_n1 = _serial_to_name(sfocsp, cert_glob_pattern) s_to_n.update(s_to_n1) SFOCSP.OCSP_CACHE.read_ocsp_response_cache_file(sfocsp, ocsp_response_cache_file) def custom_key(k): # third element is Serial Number for the subject serial_number = core.Integer.load(k[2]) return int(serial_number.native) output = {} ocsp_validation_cache = SFOCSP.OCSP_CACHE.CACHE for hkey in sorted(ocsp_validation_cache, key=custom_key): json_key = sfocsp.encode_cert_id_base64(hkey) serial_number = core.Integer.load(hkey[2]).native if int(serial_number) in s_to_n: name = s_to_n[int(serial_number)] else: name = "Unknown" output[json_key] = { "serial_number": format(serial_number, "d"), "name": name, } value = ocsp_validation_cache[hkey] cache = value[1] ocsp_response = ocsp.OCSPResponse.load(cache) basic_ocsp_response = ocsp_response.basic_ocsp_response tbs_response_data = basic_ocsp_response["tbs_response_data"] current_time = int(time()) for single_response in tbs_response_data["responses"]: created_on = int(value[0]) produce_at = tbs_response_data["produced_at"].native this_update = single_response["this_update"].native next_update = single_response["next_update"].native if current_time - OCSP_CACHE_SERVER_INTERVAL > created_on: raise_old_cache_exception(current_time, created_on, name, serial_number) next_update_utc = (next_update.replace(tzinfo=None) - ZERO_EPOCH).total_seconds() this_update_utc = (this_update.replace(tzinfo=None) - ZERO_EPOCH).total_seconds() if current_time > next_update_utc or current_time < this_update_utc: raise_outdated_validity_exception(current_time, name, serial_number, this_update, next_update) output[json_key]["created_on"] = strftime( SFOCSP.OUTPUT_TIMESTAMP_FORMAT, gmtime(created_on)) output[json_key]["produce_at"] = str(produce_at) output[json_key]["this_update"] = str(this_update) output[json_key]["next_update"] = str(next_update) print(json.dumps(output))