backend.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. # This file is dual licensed under the terms of the Apache License, Version
  2. # 2.0, and the BSD License. See the LICENSE file in the root of this repository
  3. # for complete details.
  4. from __future__ import annotations
  5. from cryptography.hazmat.bindings._rust import openssl as rust_openssl
  6. from cryptography.hazmat.bindings.openssl import binding
  7. from cryptography.hazmat.primitives import hashes
  8. from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding
  9. from cryptography.hazmat.primitives.asymmetric import ec
  10. from cryptography.hazmat.primitives.asymmetric import utils as asym_utils
  11. from cryptography.hazmat.primitives.asymmetric.padding import (
  12. MGF1,
  13. OAEP,
  14. PSS,
  15. PKCS1v15,
  16. )
  17. from cryptography.hazmat.primitives.ciphers import (
  18. CipherAlgorithm,
  19. )
  20. from cryptography.hazmat.primitives.ciphers.algorithms import (
  21. AES,
  22. )
  23. from cryptography.hazmat.primitives.ciphers.modes import (
  24. CBC,
  25. Mode,
  26. )
  27. class Backend:
  28. """
  29. OpenSSL API binding interfaces.
  30. """
  31. name = "openssl"
  32. # TripleDES encryption is disallowed/deprecated throughout 2023 in
  33. # FIPS 140-3. To keep it simple we denylist any use of TripleDES (TDEA).
  34. _fips_ciphers = (AES,)
  35. # Sometimes SHA1 is still permissible. That logic is contained
  36. # within the various *_supported methods.
  37. _fips_hashes = (
  38. hashes.SHA224,
  39. hashes.SHA256,
  40. hashes.SHA384,
  41. hashes.SHA512,
  42. hashes.SHA512_224,
  43. hashes.SHA512_256,
  44. hashes.SHA3_224,
  45. hashes.SHA3_256,
  46. hashes.SHA3_384,
  47. hashes.SHA3_512,
  48. hashes.SHAKE128,
  49. hashes.SHAKE256,
  50. )
  51. _fips_ecdh_curves = (
  52. ec.SECP224R1,
  53. ec.SECP256R1,
  54. ec.SECP384R1,
  55. ec.SECP521R1,
  56. )
  57. _fips_rsa_min_key_size = 2048
  58. _fips_rsa_min_public_exponent = 65537
  59. _fips_dsa_min_modulus = 1 << 2048
  60. _fips_dh_min_key_size = 2048
  61. _fips_dh_min_modulus = 1 << _fips_dh_min_key_size
  62. def __init__(self) -> None:
  63. self._binding = binding.Binding()
  64. self._ffi = self._binding.ffi
  65. self._lib = self._binding.lib
  66. self._fips_enabled = rust_openssl.is_fips_enabled()
  67. def __repr__(self) -> str:
  68. return (
  69. f"<OpenSSLBackend(version: {self.openssl_version_text()}, "
  70. f"FIPS: {self._fips_enabled}, "
  71. f"Legacy: {rust_openssl._legacy_provider_loaded})>"
  72. )
  73. def openssl_assert(self, ok: bool) -> None:
  74. return binding._openssl_assert(ok)
  75. def _enable_fips(self) -> None:
  76. # This function enables FIPS mode for OpenSSL 3.0.0 on installs that
  77. # have the FIPS provider installed properly.
  78. rust_openssl.enable_fips(rust_openssl._providers)
  79. assert rust_openssl.is_fips_enabled()
  80. self._fips_enabled = rust_openssl.is_fips_enabled()
  81. def openssl_version_text(self) -> str:
  82. """
  83. Friendly string name of the loaded OpenSSL library. This is not
  84. necessarily the same version as it was compiled against.
  85. Example: OpenSSL 3.2.1 30 Jan 2024
  86. """
  87. return rust_openssl.openssl_version_text()
  88. def openssl_version_number(self) -> int:
  89. return rust_openssl.openssl_version()
  90. def hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
  91. if self._fips_enabled and not isinstance(algorithm, self._fips_hashes):
  92. return False
  93. return rust_openssl.hashes.hash_supported(algorithm)
  94. def signature_hash_supported(
  95. self, algorithm: hashes.HashAlgorithm
  96. ) -> bool:
  97. # Dedicated check for hashing algorithm use in message digest for
  98. # signatures, e.g. RSA PKCS#1 v1.5 SHA1 (sha1WithRSAEncryption).
  99. if self._fips_enabled and isinstance(algorithm, hashes.SHA1):
  100. return False
  101. return self.hash_supported(algorithm)
  102. def scrypt_supported(self) -> bool:
  103. if self._fips_enabled:
  104. return False
  105. else:
  106. return hasattr(rust_openssl.kdf.Scrypt, "derive")
  107. def argon2_supported(self) -> bool:
  108. if self._fips_enabled:
  109. return False
  110. else:
  111. return hasattr(rust_openssl.kdf.Argon2id, "derive")
  112. def hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
  113. # FIPS mode still allows SHA1 for HMAC
  114. if self._fips_enabled and isinstance(algorithm, hashes.SHA1):
  115. return True
  116. if rust_openssl.CRYPTOGRAPHY_IS_AWSLC:
  117. return isinstance(
  118. algorithm,
  119. (
  120. hashes.SHA1,
  121. hashes.SHA224,
  122. hashes.SHA256,
  123. hashes.SHA384,
  124. hashes.SHA512,
  125. hashes.SHA512_224,
  126. hashes.SHA512_256,
  127. ),
  128. )
  129. return self.hash_supported(algorithm)
  130. def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool:
  131. if self._fips_enabled:
  132. # FIPS mode requires AES. TripleDES is disallowed/deprecated in
  133. # FIPS 140-3.
  134. if not isinstance(cipher, self._fips_ciphers):
  135. return False
  136. return rust_openssl.ciphers.cipher_supported(cipher, mode)
  137. def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
  138. return self.hmac_supported(algorithm)
  139. def _consume_errors(self) -> list[rust_openssl.OpenSSLError]:
  140. return rust_openssl.capture_error_stack()
  141. def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
  142. if self._fips_enabled and isinstance(algorithm, hashes.SHA1):
  143. return False
  144. return isinstance(
  145. algorithm,
  146. (
  147. hashes.SHA1,
  148. hashes.SHA224,
  149. hashes.SHA256,
  150. hashes.SHA384,
  151. hashes.SHA512,
  152. ),
  153. )
  154. def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool:
  155. if isinstance(padding, PKCS1v15):
  156. return True
  157. elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1):
  158. # FIPS 186-4 only allows salt length == digest length for PSS
  159. # It is technically acceptable to set an explicit salt length
  160. # equal to the digest length and this will incorrectly fail, but
  161. # since we don't do that in the tests and this method is
  162. # private, we'll ignore that until we need to do otherwise.
  163. if (
  164. self._fips_enabled
  165. and padding._salt_length != PSS.DIGEST_LENGTH
  166. ):
  167. return False
  168. return self.hash_supported(padding._mgf._algorithm)
  169. elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1):
  170. return self._oaep_hash_supported(
  171. padding._mgf._algorithm
  172. ) and self._oaep_hash_supported(padding._algorithm)
  173. else:
  174. return False
  175. def rsa_encryption_supported(self, padding: AsymmetricPadding) -> bool:
  176. if self._fips_enabled and isinstance(padding, PKCS1v15):
  177. return False
  178. else:
  179. return self.rsa_padding_supported(padding)
  180. def dsa_supported(self) -> bool:
  181. return (
  182. not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  183. and not self._fips_enabled
  184. )
  185. def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
  186. if not self.dsa_supported():
  187. return False
  188. return self.signature_hash_supported(algorithm)
  189. def cmac_algorithm_supported(self, algorithm) -> bool:
  190. return self.cipher_supported(
  191. algorithm, CBC(b"\x00" * algorithm.block_size)
  192. )
  193. def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool:
  194. if self._fips_enabled and not isinstance(
  195. curve, self._fips_ecdh_curves
  196. ):
  197. return False
  198. return rust_openssl.ec.curve_supported(curve)
  199. def elliptic_curve_signature_algorithm_supported(
  200. self,
  201. signature_algorithm: ec.EllipticCurveSignatureAlgorithm,
  202. curve: ec.EllipticCurve,
  203. ) -> bool:
  204. # We only support ECDSA right now.
  205. if not isinstance(signature_algorithm, ec.ECDSA):
  206. return False
  207. return self.elliptic_curve_supported(curve) and (
  208. isinstance(signature_algorithm.algorithm, asym_utils.Prehashed)
  209. or self.hash_supported(signature_algorithm.algorithm)
  210. )
  211. def elliptic_curve_exchange_algorithm_supported(
  212. self, algorithm: ec.ECDH, curve: ec.EllipticCurve
  213. ) -> bool:
  214. return self.elliptic_curve_supported(curve) and isinstance(
  215. algorithm, ec.ECDH
  216. )
  217. def dh_supported(self) -> bool:
  218. return (
  219. not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  220. and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC
  221. )
  222. def dh_x942_serialization_supported(self) -> bool:
  223. return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1
  224. def x25519_supported(self) -> bool:
  225. return not self._fips_enabled
  226. def x448_supported(self) -> bool:
  227. if self._fips_enabled:
  228. return False
  229. return (
  230. not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL
  231. and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  232. and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC
  233. )
  234. def ed25519_supported(self) -> bool:
  235. return not self._fips_enabled
  236. def ed448_supported(self) -> bool:
  237. if self._fips_enabled:
  238. return False
  239. return (
  240. not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL
  241. and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  242. and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC
  243. )
  244. def ecdsa_deterministic_supported(self) -> bool:
  245. return (
  246. rust_openssl.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER
  247. and not self._fips_enabled
  248. )
  249. def poly1305_supported(self) -> bool:
  250. return not self._fips_enabled
  251. def pkcs7_supported(self) -> bool:
  252. return (
  253. not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  254. and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC
  255. )
  256. backend = Backend()