예제 #1
0
    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
예제 #3
0
    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
예제 #5
0
    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
예제 #7
0
 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
예제 #11
0
 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)
예제 #15
0
    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