backend.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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.MD5,
  121. hashes.SHA1,
  122. hashes.SHA224,
  123. hashes.SHA256,
  124. hashes.SHA384,
  125. hashes.SHA512,
  126. hashes.SHA512_224,
  127. hashes.SHA512_256,
  128. ),
  129. )
  130. return self.hash_supported(algorithm)
  131. def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool:
  132. if self._fips_enabled:
  133. # FIPS mode requires AES. TripleDES is disallowed/deprecated in
  134. # FIPS 140-3.
  135. if not isinstance(cipher, self._fips_ciphers):
  136. return False
  137. return rust_openssl.ciphers.cipher_supported(cipher, mode)
  138. def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
  139. return self.hmac_supported(algorithm)
  140. def _consume_errors(self) -> list[rust_openssl.OpenSSLError]:
  141. return rust_openssl.capture_error_stack()
  142. def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
  143. if self._fips_enabled and isinstance(algorithm, hashes.SHA1):
  144. return False
  145. return isinstance(
  146. algorithm,
  147. (
  148. hashes.SHA1,
  149. hashes.SHA224,
  150. hashes.SHA256,
  151. hashes.SHA384,
  152. hashes.SHA512,
  153. ),
  154. )
  155. def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool:
  156. if isinstance(padding, PKCS1v15):
  157. return True
  158. elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1):
  159. # FIPS 186-4 only allows salt length == digest length for PSS
  160. # It is technically acceptable to set an explicit salt length
  161. # equal to the digest length and this will incorrectly fail, but
  162. # since we don't do that in the tests and this method is
  163. # private, we'll ignore that until we need to do otherwise.
  164. if (
  165. self._fips_enabled
  166. and padding._salt_length != PSS.DIGEST_LENGTH
  167. ):
  168. return False
  169. return self.hash_supported(padding._mgf._algorithm)
  170. elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1):
  171. return self._oaep_hash_supported(
  172. padding._mgf._algorithm
  173. ) and self._oaep_hash_supported(padding._algorithm)
  174. else:
  175. return False
  176. def rsa_encryption_supported(self, padding: AsymmetricPadding) -> bool:
  177. if self._fips_enabled and isinstance(padding, PKCS1v15):
  178. return False
  179. else:
  180. return self.rsa_padding_supported(padding)
  181. def dsa_supported(self) -> bool:
  182. return (
  183. not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  184. and not self._fips_enabled
  185. )
  186. def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
  187. if not self.dsa_supported():
  188. return False
  189. return self.signature_hash_supported(algorithm)
  190. def cmac_algorithm_supported(self, algorithm) -> bool:
  191. return self.cipher_supported(
  192. algorithm, CBC(b"\x00" * algorithm.block_size)
  193. )
  194. def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool:
  195. if self._fips_enabled and not isinstance(
  196. curve, self._fips_ecdh_curves
  197. ):
  198. return False
  199. return rust_openssl.ec.curve_supported(curve)
  200. def elliptic_curve_signature_algorithm_supported(
  201. self,
  202. signature_algorithm: ec.EllipticCurveSignatureAlgorithm,
  203. curve: ec.EllipticCurve,
  204. ) -> bool:
  205. # We only support ECDSA right now.
  206. if not isinstance(signature_algorithm, ec.ECDSA):
  207. return False
  208. return self.elliptic_curve_supported(curve) and (
  209. isinstance(signature_algorithm.algorithm, asym_utils.Prehashed)
  210. or self.hash_supported(signature_algorithm.algorithm)
  211. )
  212. def elliptic_curve_exchange_algorithm_supported(
  213. self, algorithm: ec.ECDH, curve: ec.EllipticCurve
  214. ) -> bool:
  215. return self.elliptic_curve_supported(curve) and isinstance(
  216. algorithm, ec.ECDH
  217. )
  218. def dh_supported(self) -> bool:
  219. return (
  220. not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  221. and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC
  222. )
  223. def dh_x942_serialization_supported(self) -> bool:
  224. return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1
  225. def x25519_supported(self) -> bool:
  226. return not self._fips_enabled
  227. def x448_supported(self) -> bool:
  228. if self._fips_enabled:
  229. return False
  230. return (
  231. not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL
  232. and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  233. and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC
  234. )
  235. def mlkem_supported(self) -> bool:
  236. return (
  237. rust_openssl.CRYPTOGRAPHY_IS_AWSLC
  238. or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  239. or rust_openssl.CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
  240. )
  241. def mldsa_supported(self) -> bool:
  242. return (
  243. rust_openssl.CRYPTOGRAPHY_IS_AWSLC
  244. or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  245. or rust_openssl.CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
  246. )
  247. def ed25519_supported(self) -> bool:
  248. return True
  249. def ed448_supported(self) -> bool:
  250. return (
  251. not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL
  252. and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
  253. and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC
  254. )
  255. def ecdsa_deterministic_supported(self) -> bool:
  256. return (
  257. rust_openssl.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER
  258. and not self._fips_enabled
  259. )
  260. def poly1305_supported(self) -> bool:
  261. return not self._fips_enabled
  262. def pkcs7_supported(self) -> bool:
  263. return True
  264. backend = Backend()