ocsp_cache.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. # Copyright 2020-present MongoDB, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Utilities for caching OCSP responses."""
  15. from collections import namedtuple
  16. from datetime import datetime as _datetime
  17. from threading import Lock
  18. class _OCSPCache(object):
  19. """A cache for OCSP responses."""
  20. CACHE_KEY_TYPE = namedtuple('OcspResponseCacheKey',
  21. ['hash_algorithm', 'issuer_name_hash',
  22. 'issuer_key_hash', 'serial_number'])
  23. def __init__(self):
  24. self._data = {}
  25. # Hold this lock when accessing _data.
  26. self._lock = Lock()
  27. def _get_cache_key(self, ocsp_request):
  28. return self.CACHE_KEY_TYPE(
  29. hash_algorithm=ocsp_request.hash_algorithm.name.lower(),
  30. issuer_name_hash=ocsp_request.issuer_name_hash,
  31. issuer_key_hash=ocsp_request.issuer_key_hash,
  32. serial_number=ocsp_request.serial_number)
  33. def __setitem__(self, key, value):
  34. """Add/update a cache entry.
  35. 'key' is of type cryptography.x509.ocsp.OCSPRequest
  36. 'value' is of type cryptography.x509.ocsp.OCSPResponse
  37. Validity of the OCSP response must be checked by caller.
  38. """
  39. with self._lock:
  40. cache_key = self._get_cache_key(key)
  41. # As per the OCSP protocol, if the response's nextUpdate field is
  42. # not set, the responder is indicating that newer revocation
  43. # information is available all the time.
  44. if value.next_update is None:
  45. self._data.pop(cache_key, None)
  46. return
  47. # Do nothing if the response is invalid.
  48. if not (value.this_update <= _datetime.utcnow()
  49. < value.next_update):
  50. return
  51. # Cache new response OR update cached response if new response
  52. # has longer validity.
  53. cached_value = self._data.get(cache_key, None)
  54. if (cached_value is None or
  55. cached_value.next_update < value.next_update):
  56. self._data[cache_key] = value
  57. def __getitem__(self, item):
  58. """Get a cache entry if it exists.
  59. 'item' is of type cryptography.x509.ocsp.OCSPRequest
  60. Raises KeyError if the item is not in the cache.
  61. """
  62. with self._lock:
  63. cache_key = self._get_cache_key(item)
  64. value = self._data[cache_key]
  65. # Return cached response if it is still valid.
  66. if (value.this_update <= _datetime.utcnow() <
  67. value.next_update):
  68. return value
  69. self._data.pop(cache_key, None)
  70. raise KeyError(cache_key)