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