Repository: pyca/cryptography

Issue:
Expose actual salt_length if padding.PSS.MAX_LENGTH is used
https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/  suggests to use salt_length=padding.PSS.MAX_LENGTH for signing with PSS padding.
To verify a signature, the salt_length must be known and cannot be determined from the signature.

Assuming in the verifier that the signer used MAX_LENGTH is not a proper solution as the signer is free to use different salt lengths. Some implementations may not even support the MAX_LENGTH feature, e.g. I couldn't find anything in BouncyCastle so far.

RFC3447 which specified the PSS algorithm also specifies an ASN.1 structure "RSASSA-PSS-params" for use in the "AlgorithmIdentifier" structure. It contains the parameters used for creating the signature, including the salt length.

I think we need a way to expose the result of _get_rsa_pss_salt_length to the user when creating a signature.
Alternatively, remove padding.PSS.MAX_LENGTH and add a function which computes this value so the user can pass the result back with the salt_length parameter.



Discussion:
`rsa.calculate_max_pss_salt_length(key, hash_algorithm)` would do this. Takes the key object and an instance of the hash algorithm and returns an integer. We can then switch the OpenSSL backend to using it.

that sounds ok to me


Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py
--- a/src/cryptography/hazmat/backends/openssl/rsa.py
+++ b/src/cryptography/hazmat/backends/openssl/rsa.py
@@ -15,22 +15,18 @@
     AsymmetricSignatureContext, AsymmetricVerificationContext, rsa
 )
 from cryptography.hazmat.primitives.asymmetric.padding import (
-    AsymmetricPadding, MGF1, OAEP, PKCS1v15, PSS
+    AsymmetricPadding, MGF1, OAEP, PKCS1v15, PSS, calculate_max_pss_salt_length
 )
 from cryptography.hazmat.primitives.asymmetric.rsa import (
     RSAPrivateKeyWithSerialization, RSAPublicKeyWithSerialization
 )
 
 
-def _get_rsa_pss_salt_length(pss, key_size, digest_size):
+def _get_rsa_pss_salt_length(pss, key, hash_algorithm):
     salt = pss._salt_length
 
     if salt is MGF1.MAX_LENGTH or salt is PSS.MAX_LENGTH:
-        # bit length - 1 per RFC 3447
-        emlen = int(math.ceil((key_size - 1) / 8.0))
-        salt_length = emlen - digest_size - 2
-        assert salt_length >= 0
-        return salt_length
+        return calculate_max_pss_salt_length(key, hash_algorithm)
     else:
         return salt
 
@@ -220,8 +216,8 @@ def finalize(self):
                 pkey_ctx,
                 _get_rsa_pss_salt_length(
                     self._padding,
-                    self._private_key.key_size,
-                    self._hash_ctx.algorithm.digest_size
+                    self._private_key,
+                    self._hash_ctx.algorithm,
                 )
             )
             self._backend.openssl_assert(res > 0)
@@ -348,8 +344,8 @@ def verify(self):
                 pkey_ctx,
                 _get_rsa_pss_salt_length(
                     self._padding,
-                    self._public_key.key_size,
-                    self._hash_ctx.algorithm.digest_size
+                    self._public_key,
+                    self._hash_ctx.algorithm,
                 )
             )
             self._backend.openssl_assert(res > 0)
diff --git a/src/cryptography/hazmat/primitives/asymmetric/padding.py b/src/cryptography/hazmat/primitives/asymmetric/padding.py
--- a/src/cryptography/hazmat/primitives/asymmetric/padding.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/padding.py
@@ -5,11 +5,13 @@
 from __future__ import absolute_import, division, print_function
 
 import abc
+import math
 
 import six
 
 from cryptography import utils
 from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.asymmetric import rsa
 
 
 @six.add_metaclass(abc.ABCMeta)
@@ -65,3 +67,13 @@ def __init__(self, algorithm):
             raise TypeError("Expected instance of hashes.HashAlgorithm.")
 
         self._algorithm = algorithm
+
+
+def calculate_max_pss_salt_length(key, hash_algorithm):
+    if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)):
+        raise TypeError("key must be an RSA public or private key")
+    # bit length - 1 per RFC 3447
+    emlen = int(math.ceil((key.key_size - 1) / 8.0))
+    salt_length = emlen - hash_algorithm.digest_size - 2
+    assert salt_length >= 0
+    return salt_length


Test patch:
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -1081,6 +1081,10 @@ class TestRSAPKCS1Verification(object):
 
 
 class TestPSS(object):
+    def test_calculate_max_pss_salt_length(self):
+        with pytest.raises(TypeError):
+            padding.calculate_max_pss_salt_length(object(), hashes.SHA256())
+
     def test_invalid_salt_length_not_integer(self):
         with pytest.raises(TypeError):
             padding.PSS(