def _read_ocsp_response_cache(ocsp_response_cache_uri): """ Read OCSP Response cache data from the URI, which is very likely a file. :param ocsp_response_cache_uri: OCSP response cache data from URI """ if ocsp_response_cache_uri is not None: try: with OCSP_VALIDATION_CACHE_LOCK: parsed_url = urlsplit(ocsp_response_cache_uri) if parsed_url.scheme == 'file': read_ocsp_response_cache_file( path.join(parsed_url.netloc, parsed_url.path), OCSP_VALIDATION_CACHE) else: raise Exception("Unsupported OCSP URI: %s", ocsp_response_cache_uri) except Exception as e: logger.debug( "Failed to read OCSP response cache file %s: %s, " "No worry. It will validate with OCSP server. " "Ignoring...", ocsp_response_cache_uri, e, exc_info=True)
def _reset_ocsp_dynamic_cache_server_url(): """ Reset OCSP dynamic cache server url pattern. This is used only when OCSP cache server is updated. """ global SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN global SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN_LOCK with SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN_LOCK: if SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN is None and \ not SF_OCSP_RESPONSE_CACHE_SERVER_URL.startswith( DEFAULT_OCSP_RESPONSE_CACHE_SERVER_URL): # only if custom OCSP cache server is used. parsed_url = urlsplit(SF_OCSP_RESPONSE_CACHE_SERVER_URL) if parsed_url.port: SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = \ u"{0}://{1}:{2}/retry/".format( parsed_url.scheme, parsed_url.hostname, parsed_url.port) + u"{0}/{1}" else: SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = \ u"{0}://{1}/retry/".format( parsed_url.scheme, parsed_url.hostname) + u"{0}/{1}" logger.debug("OCSP dynamic cache server URL pattern: %s", SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN)
def _reset_ocsp_dynamic_cache_server_url(): """ Reset OCSP dynamic cache server url pattern. This is used only when OCSP cache server is updated. """ with OCSPCache.RETRY_URL_PATTERN_LOCK: if OCSPCache.RETRY_URL_PATTERN is None: if not OCSPCache.CACHE_SERVER_URL.startswith( OCSPCache.DEFAULT_CACHE_SERVER_URL): # only if custom OCSP cache server is used. parsed_url = urlsplit(OCSPCache.CACHE_SERVER_URL) if parsed_url.port: OCSPCache.RETRY_URL_PATTERN = \ u"{0}://{1}:{2}/retry/".format( parsed_url.scheme, parsed_url.hostname, parsed_url.port) + u"{0}/{1}" else: OCSPCache.RETRY_URL_PATTERN = \ u"{0}://{1}/retry/".format( parsed_url.scheme, parsed_url.hostname) + u"{0}/{1}" elif OCSPCache.ACTIVATE_SSD: OCSPCache.RETRY_URL_PATTERN = OCSPCache.DEFAULT_RETRY_URL logger.debug("OCSP dynamic cache server URL pattern: %s", OCSPCache.RETRY_URL_PATTERN)
def update_ocsp_response_cache_file(ocsp, ocsp_response_cache_uri): """ Updates OCSP Response Cache """ if ocsp_response_cache_uri is not None: try: parsed_url = urlsplit(ocsp_response_cache_uri) if parsed_url.scheme == 'file': filename = path.join(parsed_url.netloc, parsed_url.path) lock_dir = filename + '.lck' for _ in range(100): # wait until the lck file has been removed # or up to 1 second (0.01 x 100) if OCSPCache.lock_cache_file(lock_dir): break time.sleep(0.01) try: OCSPCache.write_ocsp_response_cache_file( ocsp, filename) finally: OCSPCache.unlock_cache_file(lock_dir) else: logger.debug( "No OCSP response cache file is written, because the " "given URI is not a file: %s. Ignoring...", ocsp_response_cache_uri) except Exception as e: logger.debug( "Failed to write OCSP response cache " "file. file: %s, error: %s, Ignoring...", ocsp_response_cache_uri, e, exc_info=True)
def generate_get_url(ocsp_url, b64data): if OCSPCache.RETRY_URL_PATTERN: parsed_url = urlsplit(ocsp_url) target_url = OCSPCache.RETRY_URL_PATTERN.format( parsed_url.hostname, b64data) else: target_url = u"{0}/{1}".format(ocsp_url, b64data) return target_url
def delete_cache_file(): """ Delete the cache file. Used by tests only """ parsed_url = urlsplit(OCSPCache.OCSP_RESPONSE_CACHE_URI) fname = path.join(parsed_url.netloc, parsed_url.path) OCSPCache.lock_cache_file(fname) try: os.unlink(fname) finally: OCSPCache.unlock_cache_file(fname)
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 _fetch_ocsp_response(req, cert, do_retry=True): """ Fetch OCSP response using OCSPRequest """ global SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN max_retry = 100 if do_retry else 1 data = req.dump() # convert to DER b64data = b64encode(data).decode('ascii') urls = cert.ocsp_urls ocsp_url = urls[0] if SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN: parsed_url = urlsplit(ocsp_url) target_url = SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN.format( parsed_url.hostname, b64data ) else: target_url = u"{0}/{1}".format(ocsp_url, b64data) ret = None logger.debug('url: %s', target_url) with requests.Session() as session: session.mount('http://', adapters.HTTPAdapter(max_retries=5)) session.mount('https://', adapters.HTTPAdapter(max_retries=5)) global PROXIES for attempt in range(max_retry): response = session.get( target_url, proxies=PROXIES, timeout=30) if response.status_code == OK: logger.debug( "OCSP response was successfully returned from OCSP server.") ret = response.content break elif max_retry > 1: wait_time = 2 ** attempt wait_time = 16 if wait_time > 16 else wait_time logger.debug("OCSP server returned %s. Retrying in %s(s)", response.status_code, wait_time) time.sleep(wait_time) else: logger.error("Failed to get OCSP response after %s attempt.", max_retry) raise OperationalError( msg="Failed to get OCSP response after {) attempt.".format( max_retry), errno=ER_INVALID_OCSP_RESPONSE ) return ret
def _fetch_ocsp_response(req, cert, do_retry=True): """ Fetch OCSP response using OCSPRequest """ urls = cert.ocsp_urls parsed_url = urlsplit(urls[0]) # urls is guaranteed to have OCSP URL max_retry = 100 if do_retry else 1 data = req.dump() # convert to DER headers = { 'Content-Type': 'application/ocsp-request', 'Content-Length': '{0}'.format(len(data)), 'Host': parsed_url.hostname, } ret = None with requests.Session() as session: session.mount('http://', adapters.HTTPAdapter(max_retries=5)) session.mount('https://', adapters.HTTPAdapter(max_retries=5)) global PROXIES for attempt in range(max_retry): response = session.post(urls[0], headers=headers, proxies=PROXIES, data=data, timeout=30) if response.status_code == OK: logger.debug("OCSP response was successfully returned from " "OCSP server.") ret = response.content break elif max_retry > 1: wait_time = 2**attempt wait_time = 16 if wait_time > 16 else wait_time logger.debug("OCSP server returned %s. Retrying in %s(s)", response.status_code, wait_time) time.sleep(wait_time) else: logger.error("Failed to get OCSP response after %s attempt.", max_retry) raise OperationalError( msg="Failed to get OCSP response after {) attempt.".format( max_retry), errno=ER_INVALID_OCSP_RESPONSE) return ret
def main(): from OpenSSL.crypto import dump_certificate, FILETYPE_PEM def help(): print("Export certificate on the URL") print(""" Usage: {0} <url> """.format(path.basename(sys.argv[0]))) sys.exit(2) if len(sys.argv) < 2: help() input_url = sys.argv[1] parsed_url = urlsplit(input_url) connection = _openssl_connect(parsed_url.hostname, parsed_url.port or 443) for cert_openssl in connection.get_peer_cert_chain(): cert_pem = dump_certificate(FILETYPE_PEM, cert_openssl) print(cert_pem.decode('utf-8'))
def update_ocsp_response_cache_file(ocsp_response_cache_uri): """ Updates OCSP Response Cache """ lock_dir = None if ocsp_response_cache_uri is not None: try: parsed_url = urlsplit(ocsp_response_cache_uri) if parsed_url.scheme == 'file': filename = path.join(parsed_url.netloc, parsed_url.path) lock_dir = filename + '.lck' for _ in range(100): # wait until the lck file has been removed # or up to 1 second (0.01 x 100) if _lock_cache_file(lock_dir): break time.sleep(0.01) try: write_ocsp_response_cache_file(filename, OCSP_VALIDATION_CACHE) finally: _unlock_cache_file(lock_dir) lock_dir = None else: logger.debug( "No OCSP response cache file is written, because the " "given URI is not a file: %s. Ignoring...", ocsp_response_cache_uri) except Exception as e: logger.debug( "Failed to write OCSP response cache " "file. file: %s, error: %s, Ignoring...", ocsp_response_cache_uri, e, exc_info=True) if lock_dir is not None and os.path.exists(lock_dir): # final attempt to delete the lock directory if not _unlock_cache_file(lock_dir): logger.debug( "Failed to remove OCSP response cache lock directory. " "Ignoring...")
def read_file(ocsp): """ Read OCSP Response cache data from the URI, which is very likely a file. """ try: parsed_url = urlsplit(OCSPCache.OCSP_RESPONSE_CACHE_URI) if parsed_url.scheme == 'file': OCSPCache.read_ocsp_response_cache_file( ocsp, path.join(parsed_url.netloc, parsed_url.path)) else: raise Exception("Unsupported OCSP URI: %s", OCSPCache.OCSP_RESPONSE_CACHE_URI) except Exception as e: logger.debug( "Failed to read OCSP response cache file %s: %s, " "No worry. It will validate with OCSP server. " "Ignoring...", OCSPCache.OCSP_RESPONSE_CACHE_URI, e, exc_info=True)
def dump_ocsp_response(urls, output_filename): for url in urls: parsed_url = urlsplit(url) hostname = parsed_url.hostname port = parsed_url.port or 443 connection = _openssl_connect(hostname, port) cert_data = _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, _ = _create_ocsp_request(issuer, subject) _, cert_id, ocsp_response_der = validate_by_direct_connection( issuer, subject) ocsp_response = ocsp.OCSPResponse.load(ocsp_response_der) print( "------------------------------------------------------------") print("Issuer Name: {0}".format(issuer.subject.native)) print("Subject Name: {0}".format(subject.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': print("This Update: {0}".format( single_response['this_update'].native)) print("Next Update: {0}".format( single_response['next_update'].native)) this_update = ( single_response['this_update'].native.replace( tzinfo=None) - ZERO_EPOCH).total_seconds() next_update = ( single_response['next_update'].native.replace( tzinfo=None) - ZERO_EPOCH).total_seconds() tolerable_validity = _calculate_tolerable_validity( this_update, next_update) print("Tolerable Update: {0}".format( strftime('%Y%m%d%H%M%SZ', gmtime( next_update + tolerable_validity)) )) if _is_validaity_range(current_time, this_update, next_update): print("OK") else: print(_validity_error_message( current_time, this_update, next_update)) elif cert_status == 'revoked': revoked_info = single_response['cert_status'] revocation_time = revoked_info.native['revocation_time'] revocation_reason = revoked_info.native['revocation_reason'] print("Revoked Time: {0}".format( revocation_time.strftime(OUTPUT_TIMESTAMP_FORMAT))) print("Revoked Reason: {0}".format(revocation_reason)) print("Revoked") else: print("Unknown") print('') if output_filename: write_ocsp_response_cache_file( output_filename, OCSP_VALIDATION_CACHE) return OCSP_VALIDATION_CACHE