def _process_good_status(self, single_response, cert_id, ocsp_response): """ Process GOOD status """ current_time = int(time.time()) this_update_native, next_update_native = \ self.extract_good_status(single_response) if this_update_native is None or next_update_native is None: raise OperationalError( msg=u"Either this update or next " u"update is None. this_update: {}, next_update: {}".format( this_update_native, next_update_native), errno=ER_INVALID_OCSP_RESPONSE) this_update = (this_update_native.replace(tzinfo=None) - SnowflakeOCSP.ZERO_EPOCH).total_seconds() next_update = (next_update_native.replace(tzinfo=None) - SnowflakeOCSP.ZERO_EPOCH).total_seconds() if not SnowflakeOCSP._is_validaity_range(current_time, this_update, next_update): raise OperationalError(msg=SnowflakeOCSP._validity_error_message( current_time, this_update, next_update), errno=ER_INVALID_OCSP_RESPONSE) SnowflakeOCSP.OCSP_CACHE.update_cache(self, cert_id, ocsp_response)
def _process_good_status(single_response, cert_id, ocsp_response): """ Process GOOD status """ current_time = int(time.time()) this_update_native = single_response['this_update'].native next_update_native = single_response['next_update'].native if this_update_native is None or next_update_native is None: raise OperationalError( msg=u"Either this update or next " u"update is None. this_update: {}, next_update: {}".format( this_update_native, next_update_native), errno=ER_INVALID_OCSP_RESPONSE) this_update = (this_update_native.replace(tzinfo=None) - ZERO_EPOCH).total_seconds() next_update = (next_update_native.replace(tzinfo=None) - ZERO_EPOCH).total_seconds() if not _is_validaity_range(current_time, this_update, next_update): raise OperationalError(msg=_validity_error_message( current_time, this_update, next_update), errno=ER_INVALID_OCSP_RESPONSE) with OCSP_VALIDATION_CACHE_LOCK: hkey = _decode_cert_id_key(cert_id) if hkey not in OCSP_VALIDATION_CACHE: OCSP_VALIDATION_CACHE[hkey] = (current_time, ocsp_response) global OCSP_VALIDATION_CACHE_UPDATED OCSP_VALIDATION_CACHE_UPDATED = True
def process_ocsp_response(self, issuer, cert_id, ocsp_response): try: res = OCSPResponse.load(ocsp_response) except Exception: raise OperationalError(msg='Invalid OCSP Response', errno=ER_INVALID_OCSP_RESPONSE) if res['response_status'].native != 'successful': raise OperationalError(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) 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 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: raise OperationalError( msg="Unknown revocation status was returned. OCSP response " "may be malformed: {0}".format(cert_status), errno=ER_INVALID_OCSP_RESPONSE_CODE)
def _create_pair_issuer_subject(cert_map): """ Creates pairs of issuer and subject certificates """ issuer_subject = [] for subject_der in cert_map: subject = cert_map[subject_der] if 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 with ROOT_CERTIFICATES_DICT_LOCK: _lazy_read_ca_bundle() logger.debug('not found issuer_der: %s', subject.issuer.native) if issuer_hash not in ROOT_CERTIFICATES_DICT: raise OperationalError( 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 = ROOT_CERTIFICATES_DICT[issuer_hash] else: issuer = cert_map[issuer_hash] issuer_subject.append((issuer, subject)) return issuer_subject
def _fetch_ocsp_response(self, ocsp_request, cert, do_retry=True): """ Fetch OCSP response using OCSPRequest """ ocsp_url = self.extract_ocsp_url(cert) if not ocsp_url: return None actual_method = 'post' if self._use_post_method else 'get' if SnowflakeOCSP.OCSP_CACHE.RETRY_URL_PATTERN: # no POST is supported for Retry URL at the moment. actual_method = 'get' if actual_method == 'get': b64data = self.decode_ocsp_request_b64(ocsp_request) target_url = SnowflakeOCSP.OCSP_CACHE.generate_get_url( ocsp_url, b64data) payload = None headers = None else: target_url = ocsp_url payload = self.decode_ocsp_request(ocsp_request) headers = {'Content-Type': 'application/ocsp-request'} 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)) max_retry = 30 if do_retry else 1 sleep_time = 1 backoff = DecorrelateJitterBackoff(sleep_time, 16) for attempt in range(max_retry): response = session.request( headers=headers, method=actual_method, url=target_url, timeout=30, data=payload, ) if response.status_code == OK: logger.debug( "OCSP response was successfully returned from OCSP " "server.") ret = response.content break elif max_retry > 1: sleep_time = backoff.next_sleep(sleep_time) logger.debug("OCSP server returned %s. Retrying in %s(s)", response.status_code, sleep_time) time.sleep(sleep_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 _validate(self, hostname, cert_data, do_retry=True): global OCSP_VALIDATION_CACHE_UPDATED global OCSP_VALIDATION_CACHE global SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED if SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED: # Validate certs sequentially if OCSP response cache server is used results = _validate_certificates_sequential(cert_data, do_retry) else: results = _validate_certificates_parallel(cert_data, do_retry) with OCSP_VALIDATION_CACHE_LOCK: if OCSP_VALIDATION_CACHE_UPDATED: update_ocsp_response_cache_file(self._ocsp_response_cache_uri) OCSP_VALIDATION_CACHE_UPDATED = False if len(results) != len(cert_data): raise OperationalError( msg="Failed to validate the certificate " "revocation status. The number of validation " "didn't match: hostname={0}, results={1}, " "cert_data={2}".format(hostname, len(results), len(cert_data)), errno=ER_INVALID_OCSP_RESPONSE) logger.debug('ok') return True
def _process_unknown_status(self, cert_id): """ Process UNKNOWN status """ SnowflakeOCSP.OCSP_CACHE.delete_cache(self, cert_id) raise OperationalError( msg=u"The certificate is in UNKNOWN revocation status.", errno=ER_SERVER_CERTIFICATE_UNKNOWN, )
def _verify_signature(signature_algorithm, signature, cert, data): rsakey = RSA.importKey(cert.public_key.unwrap().dump()) signer = PKCS1_v1_5.new(rsakey) if signature_algorithm in SIGNATURE_ALGORITHM_TO_DIGEST_CLASS: digest = 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 OperationalError(msg="Failed to verify the signature", errno=ER_INVALID_OCSP_RESPONSE)
def _process_unknown_status(cert_id): """ Process UNKNOWN status """ with OCSP_VALIDATION_CACHE_LOCK: hkey = _decode_cert_id_key(cert_id) if hkey in OCSP_VALIDATION_CACHE: global OCSP_VALIDATION_CACHE_UPDATED OCSP_VALIDATION_CACHE_UPDATED = True del OCSP_VALIDATION_CACHE[hkey] raise OperationalError( msg=u"The certificate is in UNKNOWN revocation status.", errno=ER_SERVER_CERTIFICATE_REVOKED, )
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 _process_revoked_status(self, single_response, cert_id): """ Process REVOKED status """ current_time = int(time.time()) SnowflakeOCSP.OCSP_CACHE.delete_cache(self, cert_id) revocation_time, revocation_reason = self.extract_revoked_status( single_response) raise OperationalError( msg="The certificate has been revoked: current_time={0}, " "revocation_time={1}, reason={2}".format( strftime(SnowflakeOCSP.OUTPUT_TIMESTAMP_FORMAT, gmtime(current_time)), revocation_time.strftime( SnowflakeOCSP.OUTPUT_TIMESTAMP_FORMAT), revocation_reason), errno=ER_SERVER_CERTIFICATE_REVOKED)
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 _validate_certificates_parallel(cert_data, do_retry=True): pool = ThreadPool(len(cert_data)) results = [] try: _check_ocsp_response_cacher_ser(cert_data) for issuer, subject in cert_data: r = pool.apply_async(validate_by_direct_connection, [issuer, subject, do_retry]) results.append(r) finally: pool.close() pool.join() for r in results: if not r.successful(): raise OperationalError( msg="Failed to validate the certificate " "revocation status: err={0}".format(r.get())) return results
def _process_revoked_status(single_response, cert_id): """ Process REVOKED status """ current_time = int(time.time()) with OCSP_VALIDATION_CACHE_LOCK: hkey = _decode_cert_id_key(cert_id) if hkey in OCSP_VALIDATION_CACHE: del OCSP_VALIDATION_CACHE[hkey] global OCSP_VALIDATION_CACHE_UPDATED OCSP_VALIDATION_CACHE_UPDATED = True revoked_info = single_response['cert_status'] revocation_time = revoked_info.native['revocation_time'] revocation_reason = revoked_info.native['revocation_reason'] raise OperationalError( msg="The certificate has been revoked: current_time={0}, " "revocation_time={1}, reason={2}".format( strftime(OUTPUT_TIMESTAMP_FORMAT, gmtime(current_time)), revocation_time.strftime(OUTPUT_TIMESTAMP_FORMAT), revocation_reason), errno=ER_SERVER_CERTIFICATE_REVOKED)
def is_valid_time(self, cert_id, ocsp_response): res = OCSPResponse.load(ocsp_response) if res['response_status'].native != 'successful': raise OperationalError(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