Skip to content

Commit 0adcf1c

Browse files
sfc-gh-stakedaankit-bhatnagar167
authored andcommitted
SNOW-79769: Discard expired entries in the Downloaded OCSP Cache while creating local cache.
1 parent d84076a commit 0adcf1c

File tree

4 files changed

+131
-519
lines changed

4 files changed

+131
-519
lines changed

ocsp_asn1crypto.py

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,32 @@ def extract_revoked_status(self, single_response):
144144
revocation_reason = revoked_info.native['revocation_reason']
145145
return revocation_time, revocation_reason
146146

147+
def check_cert_time_validity(self, cur_time, ocsp_cert):
148+
149+
val_start = ocsp_cert['tbs_certificate']['validity']['not_before'].native
150+
val_end = ocsp_cert['tbs_certificate']['validity']['not_after'].native
151+
152+
if cur_time > val_end or \
153+
cur_time < val_start:
154+
debug_msg = "Certificate attached to OCSP response is invalid. OCSP response " \
155+
"current time - {0} certificate not before time - {1} certificate " \
156+
"not after time - {2}. Consider running curl -o ocsp.der {3}". \
157+
format(cur_time,
158+
val_start,
159+
val_end,
160+
super(SnowflakeOCSPAsn1Crypto, self).debug_ocsp_failure_url)
161+
162+
return False, debug_msg
163+
else:
164+
return True, None
165+
166+
"""
167+
is_valid_time - checks various components of the OCSP Response
168+
for expiry.
169+
:param cert_id - certificate id corresponding to OCSP Response
170+
:param ocsp_response
171+
:return True/False depending on time validity within the response
172+
"""
147173
def is_valid_time(self, cert_id, ocsp_response):
148174
res = OCSPResponse.load(ocsp_response)
149175

@@ -153,6 +179,33 @@ def is_valid_time(self, cert_id, ocsp_response):
153179
errno=ER_INVALID_OCSP_RESPONSE)
154180

155181
basic_ocsp_response = res.basic_ocsp_response
182+
if basic_ocsp_response['certs'].native:
183+
logger.debug("Certificate is attached in Basic OCSP Response")
184+
ocsp_cert = basic_ocsp_response['certs'][0]
185+
logger.debug("Verifying the attached certificate is signed by "
186+
"the issuer")
187+
logger.debug(
188+
"Valid Not After: %s",
189+
ocsp_cert['tbs_certificate']['validity']['not_after'].native)
190+
191+
cur_time = datetime.now(timezone.utc)
192+
193+
"""
194+
Note:
195+
We purposefully do not verify certificate signature here.
196+
The OCSP Response is extracted from the OCSP Response Cache
197+
which is expected to have OCSP Responses with verified
198+
attached signature. Moreover this OCSP Response is eventually
199+
going to be processed by the driver before being consumed by
200+
the driver.
201+
This step ensures that the OCSP Response cache does not have
202+
any invalid entries.
203+
"""
204+
cert_valid, debug_msg = self.check_cert_time_validity(cur_time, ocsp_cert)
205+
if not cert_valid:
206+
logger.debug(debug_msg)
207+
return False
208+
156209
tbs_response_data = basic_ocsp_response['tbs_response_data']
157210

158211
single_response = tbs_response_data['responses'][0]
@@ -192,26 +245,22 @@ def process_ocsp_response(self, issuer, cert_id, ocsp_response):
192245

193246
cur_time = datetime.now(timezone.utc)
194247

195-
if cur_time > ocsp_cert['tbs_certificate']['validity']['not_after'].native or \
196-
cur_time < ocsp_cert['tbs_certificate']['validity']['not_before'].native:
197-
debug_msg = "Certificate attached to OCSP response is invalid. OCSP response "\
198-
"current time - {0} certificate not before time - {1} certificate "\
199-
"not after time - {2}. Consider running curl -o ocsp.der {3}".\
200-
format(cur_time,
201-
ocsp_cert['tbs_certificate']['validity']['not_before'].native,
202-
ocsp_cert['tbs_certificate']['validity']['not_after'].native,
203-
super(SnowflakeOCSPAsn1Crypto, self).debug_ocsp_failure_url)
248+
"""
249+
Signature verification should happen before any kind of
250+
validation
251+
"""
252+
self.verify_signature(
253+
ocsp_cert.hash_algo,
254+
ocsp_cert.signature,
255+
issuer,
256+
ocsp_cert['tbs_certificate'])
204257

258+
cert_valid, debug_msg = self.check_cert_time_validity(cur_time, ocsp_cert)
259+
if not cert_valid:
205260
raise RevocationCheckError(
206261
msg=debug_msg,
207262
errno=ER_INVALID_OCSP_RESPONSE_CODE)
208263

209-
self.verify_signature(
210-
ocsp_cert.hash_algo,
211-
ocsp_cert.signature,
212-
issuer,
213-
ocsp_cert['tbs_certificate']
214-
)
215264
else:
216265
logger.debug("Certificate is NOT attached in Basic OCSP Response. "
217266
"Using issuer's certificate")

ocsp_pyasn1.py

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,29 @@ def extract_revoked_status(self, single_response):
355355
def _convert_generalized_time_to_datetime(self, gentime):
356356
return datetime.strptime(str(gentime), '%Y%m%d%H%M%SZ')
357357

358+
def check_cert_time_validity(self, cur_time, tbs_certificate):
359+
cert_validity = tbs_certificate.getComponentByName('validity')
360+
cert_not_after = cert_validity.getComponentByName('notAfter')
361+
val_end = cert_not_after.getComponentByName('utcTime').asDateTime
362+
cert_not_before = cert_validity.getComponentByName('notBefore')
363+
val_start = cert_not_before.getComponentByName('utcTime').asDateTime
364+
365+
if cur_time > val_end or cur_time < val_start:
366+
debug_msg = "Certificate attached to OCSP Response is invalid. " \
367+
"OCSP response current time - {0} certificate not " \
368+
"before time - {1} certificate not after time - {2}. ". \
369+
format(cur_time, val_start, val_end)
370+
return False, debug_msg
371+
else:
372+
return True, None
373+
374+
"""
375+
is_valid_time - checks various components of the OCSP Response
376+
for expiry.
377+
:param cert_id - certificate id corresponding to OCSP Response
378+
:param ocsp_response
379+
:return True/False depending on time validity within the response
380+
"""
358381
def is_valid_time(self, cert_id, ocsp_response):
359382
res = der_decoder.decode(ocsp_response, OCSPResponse())[0]
360383

@@ -370,6 +393,35 @@ def is_valid_time(self, cert_id, ocsp_response):
370393
response_bytes.getComponentByName('response'),
371394
BasicOCSPResponse())[0]
372395

396+
attached_certs = basic_ocsp_response.getComponentByName('certs')
397+
if self._has_certs_in_ocsp_response(attached_certs):
398+
logger.debug("Certificate is attached in Basic OCSP Response")
399+
cert_der = der_encoder.encode(attached_certs[0])
400+
cert_openssl = load_certificate(FILETYPE_ASN1, cert_der)
401+
ocsp_cert = self._convert_openssl_to_pyasn1_certificate(
402+
cert_openssl)
403+
404+
cur_time = datetime.utcnow().replace(tzinfo=pytz.utc)
405+
tbs_certificate = ocsp_cert.getComponentByName('tbsCertificate')
406+
407+
"""
408+
Note:
409+
We purposefully do not verify certificate signature here.
410+
The OCSP Response is extracted from the OCSP Response Cache
411+
which is expected to have OCSP Responses with verified
412+
attached signature. Moreover this OCSP Response is eventually
413+
going to be processed by the driver before being consumed by
414+
the driver.
415+
This step ensures that the OCSP Response cache does not have
416+
any invalid entries.
417+
"""
418+
419+
cert_valid, debug_msg = self.check_cert_time_validity(cur_time,
420+
tbs_certificate)
421+
if not cert_valid:
422+
logger.debug(debug_msg)
423+
return False
424+
373425
tbs_response_data = basic_ocsp_response.getComponentByName(
374426
'tbsResponseData')
375427
single_response = tbs_response_data.getComponentByName('responses')[0]
@@ -413,27 +465,25 @@ def process_ocsp_response(self, issuer, cert_id, ocsp_response):
413465

414466
cur_time = datetime.utcnow().replace(tzinfo=pytz.utc)
415467
tbs_certificate = ocsp_cert.getComponentByName('tbsCertificate')
416-
cert_validity = tbs_certificate.getComponentByName('validity')
417-
cert_not_after = cert_validity.getComponentByName('notAfter')
418-
cert_not_after_utc = cert_not_after.getComponentByName('utcTime').asDateTime
419-
cert_not_before = cert_validity.getComponentByName('notBefore')
420-
cert_not_before_utc = cert_not_before.getComponentByName('utcTime').asDateTime
421-
422-
if cur_time > cert_not_after_utc or cur_time < cert_not_before_utc:
423-
debug_msg = "Certificate attached to OCSP Response is invalid. " \
424-
"OCSP response current time - {0} certificate not " \
425-
"before time - {1} certificate not after time - {2}. ".\
426-
format(cur_time, cert_not_before_utc, cert_not_after_utc)
427-
raise RevocationCheckError(
428-
msg=debug_msg,
429-
errno=ER_INVALID_OCSP_RESPONSE_CODE
430-
)
468+
469+
"""
470+
Signature verification should happen before any kind of
471+
validation
472+
"""
431473

432474
self.verify_signature(
433475
ocsp_cert.getComponentByName('signatureAlgorithm'),
434476
ocsp_cert.getComponentByName('signatureValue'),
435477
issuer,
436478
ocsp_cert.getComponentByName('tbsCertificate'))
479+
480+
cert_valid, debug_msg = self.check_cert_time_validity(cur_time,
481+
tbs_certificate)
482+
if not cert_valid:
483+
raise RevocationCheckError(
484+
msg=debug_msg,
485+
errno=ER_INVALID_OCSP_RESPONSE_CODE
486+
)
437487
else:
438488
logger.debug("Certificate is NOT attached in Basic OCSP Response. "
439489
"Using issuer's certificate")

ocsp_snowflake.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,8 @@ def decode_ocsp_response_cache(self, ocsp_response_cache_json):
13861386
for cert_id_base64, (
13871387
ts, ocsp_response) in ocsp_response_cache_json.items():
13881388
cert_id = self.decode_cert_id_base64(cert_id_base64)
1389+
if not self.is_valid_time(cert_id, b64decode(ocsp_response)):
1390+
continue
13891391
SnowflakeOCSP.OCSP_CACHE.update_or_delete_cache(
13901392
self, cert_id, b64decode(ocsp_response), ts)
13911393
except Exception as ex:

0 commit comments

Comments
 (0)