From f3571870b8adad2715e20ad0d2be8269dc85731a Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 8 Jan 2026 16:05:45 -0800 Subject: [PATCH 01/50] added warning --- google/auth/crypt/_python_rsa.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/google/auth/crypt/_python_rsa.py b/google/auth/crypt/_python_rsa.py index e553c25ed..1ae57aa1a 100644 --- a/google/auth/crypt/_python_rsa.py +++ b/google/auth/crypt/_python_rsa.py @@ -22,6 +22,7 @@ from __future__ import absolute_import import io +import warnings from pyasn1.codec.der import decoder # type: ignore from pyasn1_modules import pem # type: ignore @@ -39,6 +40,16 @@ _PKCS8_MARKER = ("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----") _PKCS8_SPEC = PrivateKeyInfo() +warnings.warn( + ( + "The 'rsa' library is deprecated and will be removed in a future release. " + "Please migrate to 'cryptography'. To keep using the legacy library, " + "install 'google-auth[rsa]'." + ), + category=FutureWarning, + stacklevel=2, +) + def _bit_list_to_bytes(bit_list): """Converts an iterable of 1s and 0s to bytes. From 575113c2f0c7ac9d402648054f0da7dbb985a6e2 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 8 Jan 2026 16:05:55 -0800 Subject: [PATCH 02/50] added posargs to unit test nox command --- noxfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/noxfile.py b/noxfile.py index 70173e55c..4915329ae 100644 --- a/noxfile.py +++ b/noxfile.py @@ -129,6 +129,7 @@ def unit(session): "--cov-report=term-missing", "tests", "tests_async", + *session.posargs, ) From bef613a6396091476b329485e09c6bac1a1be5b9 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 8 Jan 2026 16:06:07 -0800 Subject: [PATCH 03/50] added test --- tests/crypt/test__python_rsa.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/crypt/test__python_rsa.py b/tests/crypt/test__python_rsa.py index ee14ed9f3..6eb09472a 100644 --- a/tests/crypt/test__python_rsa.py +++ b/tests/crypt/test__python_rsa.py @@ -191,3 +191,12 @@ def test_from_service_account_file(self): assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.key.PrivateKey) + + +class TestModule(object): + def test_import_warning(self): + import importlib + from google.auth.crypt import _python_rsa + + with pytest.warns(FutureWarning, match="The 'rsa' library is deprecated"): + importlib.reload(_python_rsa) From 15e6e7b4cd8abd1826934d764e2962074da9dd0d Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 8 Jan 2026 16:13:22 -0800 Subject: [PATCH 04/50] add rsa extra --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 74036339a..f3cec848c 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,8 @@ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1739): Add bounds for urllib3 and packaging dependencies. urllib3_extra_require = ["urllib3", "packaging"] +rsa_extra_require = ["rsa>=3.1.4,<5"] + # Unit test requirements. testing_extra_require = [ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1735): Remove `grpcio` from testing requirements once an extra is added for `grpcio` dependency. @@ -85,6 +87,7 @@ "requests": requests_extra_require, "testing": testing_extra_require, "urllib3": urllib3_extra_require, + "rsa": rsa_extra_require, # TODO(https://github.com/googleapis/google-auth-library-python/issues/1735): Add an extra for `grpcio` dependency. # TODO(https://github.com/googleapis/google-auth-library-python/issues/1736): Add an extra for `oauth2client` dependency. } From 7715f92421c660ea30179b4dace70523fac854f2 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 8 Jan 2026 16:20:59 -0800 Subject: [PATCH 05/50] updated warning --- google/auth/crypt/_python_rsa.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/google/auth/crypt/_python_rsa.py b/google/auth/crypt/_python_rsa.py index 1ae57aa1a..e52d4783b 100644 --- a/google/auth/crypt/_python_rsa.py +++ b/google/auth/crypt/_python_rsa.py @@ -43,8 +43,7 @@ warnings.warn( ( "The 'rsa' library is deprecated and will be removed in a future release. " - "Please migrate to 'cryptography'. To keep using the legacy library, " - "install 'google-auth[rsa]'." + "Please migrate to 'cryptography'." ), category=FutureWarning, stacklevel=2, From ddacaf22085de248794fb2273828c01b8e5c6ae8 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 8 Jan 2026 16:21:06 -0800 Subject: [PATCH 06/50] added TODO --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index f3cec848c..95683fac7 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1739): Add bounds for urllib3 and packaging dependencies. urllib3_extra_require = ["urllib3", "packaging"] +# TODO: rsa is archived. Remove optional dependency in future release rsa_extra_require = ["rsa>=3.1.4,<5"] # Unit test requirements. From 5573db664b72e5205e6c6e8f8952c9d9dbb79a5f Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 8 Jan 2026 16:23:51 -0800 Subject: [PATCH 07/50] added docstring warnings --- google/auth/crypt/_python_rsa.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/google/auth/crypt/_python_rsa.py b/google/auth/crypt/_python_rsa.py index e52d4783b..64ca958eb 100644 --- a/google/auth/crypt/_python_rsa.py +++ b/google/auth/crypt/_python_rsa.py @@ -74,6 +74,10 @@ def _bit_list_to_bytes(bit_list): class RSAVerifier(base.Verifier): """Verifies RSA cryptographic signatures using public keys. + .. deprecated:: + The `rsa` library has been archived. Please migrate to + `cryptography`. + Args: public_key (rsa.key.PublicKey): The public key used to verify signatures. @@ -126,6 +130,10 @@ def from_string(cls, public_key): class RSASigner(base.Signer, base.FromServiceAccountMixin): """Signs messages with an RSA private key. + .. deprecated:: + The `rsa` library has been archived. Please migrate to + `cryptography`. + Args: private_key (rsa.key.PrivateKey): The private key to sign with. key_id (str): Optional key ID used to identify this private key. This From 07a4f22b36004a61fd7c526638c149578fcc2c5d Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 13:21:21 -0800 Subject: [PATCH 08/50] remove extra dependency --- setup.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/setup.py b/setup.py index 95683fac7..74036339a 100644 --- a/setup.py +++ b/setup.py @@ -46,9 +46,6 @@ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1739): Add bounds for urllib3 and packaging dependencies. urllib3_extra_require = ["urllib3", "packaging"] -# TODO: rsa is archived. Remove optional dependency in future release -rsa_extra_require = ["rsa>=3.1.4,<5"] - # Unit test requirements. testing_extra_require = [ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1735): Remove `grpcio` from testing requirements once an extra is added for `grpcio` dependency. @@ -88,7 +85,6 @@ "requests": requests_extra_require, "testing": testing_extra_require, "urllib3": urllib3_extra_require, - "rsa": rsa_extra_require, # TODO(https://github.com/googleapis/google-auth-library-python/issues/1735): Add an extra for `grpcio` dependency. # TODO(https://github.com/googleapis/google-auth-library-python/issues/1736): Add an extra for `oauth2client` dependency. } From c2ceeb41b1c8950177322cfe7c107560abe10280 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 14:58:49 -0800 Subject: [PATCH 09/50] added default rsa classes --- google/auth/crypt/_default_rsa.py | 100 ++++++++++++++++++++++++++++++ google/auth/crypt/rsa.py | 17 +++-- 2 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 google/auth/crypt/_default_rsa.py diff --git a/google/auth/crypt/_default_rsa.py b/google/auth/crypt/_default_rsa.py new file mode 100644 index 000000000..d3f7da0e1 --- /dev/null +++ b/google/auth/crypt/_default_rsa.py @@ -0,0 +1,100 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +RSASigner and RSAVerifier implementation used when neither rsa or cryptography is installed. +Implementation currently raises an ImportError directing user to install an optional dependency +""" + + +def _build_error(obj_or_cls): + cls = obj_or_cls if isinstance(obj_or_cls, type) else type(obj_or_cls) + return ImportError( + ( + f"{cls.__name__} requires `cryptography` optional dependency.", + "(Note: 'rsa' is also supported for legacy compatibility but is deprecated).", + ) + ) + + +class RSAVerifier(base.Verifier): + """Verifies RSA cryptographic signatures using public keys. + + Args: + public_key (rsa.key.PublicKey): The public key used to verify + signatures. + """ + + def __init__(self, public_key): + raise _build_error(self) + + @_helpers.copy_docstring(base.Verifier) + def verify(self, message, signature): + raise _build_error(self) + + @classmethod + def from_string(cls, public_key): + """Construct an Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + google.auth.crypt.RSAVerifier: The constructed verifier. + + Raises: + ValueError: If the public_key can't be parsed. + """ + raise _build_error(cls) + + +class RSASigner(base.Signer, base.FromServiceAccountMixin): + """Signs messages with an RSA private key. + + Args: + private_key (rsa.key.PrivateKey): The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + def __init__(self, private_key, key_id=None): + raise _build_error(self) + + @property # type: ignore + @_helpers.copy_docstring(base.Signer) + def key_id(self): + raise _build_error(self) + + @_helpers.copy_docstring(base.Signer) + def sign(self, message): + raise _build_error(self) + + @classmethod + def from_string(cls, key, key_id=None): + """Construct an Signer instance from a private key in PEM format. + + Args: + key (str): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt.Signer: The constructed signer. + + Raises: + ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in + PEM format. + """ + raise _build_error(cls) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index ed842d1eb..62864b052 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -22,9 +22,16 @@ RSASigner = _cryptography_rsa.RSASigner RSAVerifier = _cryptography_rsa.RSAVerifier except ImportError: # pragma: NO COVER - # Fallback to pure-python RSA implementation if cryptography is - # unavailable. - from google.auth.crypt import _python_rsa + try: + # Try pure-python RSA implementation if cryptography is + # unavailable. + from google.auth.crypt import _python_rsa - RSASigner = _python_rsa.RSASigner # type: ignore - RSAVerifier = _python_rsa.RSAVerifier # type: ignore + RSASigner = _python_rsa.RSASigner # type: ignore + RSAVerifier = _python_rsa.RSAVerifier # type: ignore + except ImportError: # pragma: NO COVER + # if rsa is not available, use default implementation (raises ImportError on use) + from google.auth.crypt import _default_rsa + + RSASigner = _default_rsa.RSASigner # type: ignore + RSAVerifier = _default_rsa.RSAVerifier # type: ignore From d1b3fb622b7a5ec9c72dcc9662a6d868e5cc8b4b Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 15:32:56 -0800 Subject: [PATCH 10/50] added shared wrapper class for RSASigner and RSAVerifier --- google/auth/crypt/_default_rsa.py | 100 ------------------- google/auth/crypt/rsa.py | 154 ++++++++++++++++++++++++++---- 2 files changed, 137 insertions(+), 117 deletions(-) delete mode 100644 google/auth/crypt/_default_rsa.py diff --git a/google/auth/crypt/_default_rsa.py b/google/auth/crypt/_default_rsa.py deleted file mode 100644 index d3f7da0e1..000000000 --- a/google/auth/crypt/_default_rsa.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -RSASigner and RSAVerifier implementation used when neither rsa or cryptography is installed. -Implementation currently raises an ImportError directing user to install an optional dependency -""" - - -def _build_error(obj_or_cls): - cls = obj_or_cls if isinstance(obj_or_cls, type) else type(obj_or_cls) - return ImportError( - ( - f"{cls.__name__} requires `cryptography` optional dependency.", - "(Note: 'rsa' is also supported for legacy compatibility but is deprecated).", - ) - ) - - -class RSAVerifier(base.Verifier): - """Verifies RSA cryptographic signatures using public keys. - - Args: - public_key (rsa.key.PublicKey): The public key used to verify - signatures. - """ - - def __init__(self, public_key): - raise _build_error(self) - - @_helpers.copy_docstring(base.Verifier) - def verify(self, message, signature): - raise _build_error(self) - - @classmethod - def from_string(cls, public_key): - """Construct an Verifier instance from a public key or public - certificate string. - - Args: - public_key (Union[str, bytes]): The public key in PEM format or the - x509 public key certificate. - - Returns: - google.auth.crypt.RSAVerifier: The constructed verifier. - - Raises: - ValueError: If the public_key can't be parsed. - """ - raise _build_error(cls) - - -class RSASigner(base.Signer, base.FromServiceAccountMixin): - """Signs messages with an RSA private key. - - Args: - private_key (rsa.key.PrivateKey): The private key to sign with. - key_id (str): Optional key ID used to identify this private key. This - can be useful to associate the private key with its associated - public key or certificate. - """ - - def __init__(self, private_key, key_id=None): - raise _build_error(self) - - @property # type: ignore - @_helpers.copy_docstring(base.Signer) - def key_id(self): - raise _build_error(self) - - @_helpers.copy_docstring(base.Signer) - def sign(self, message): - raise _build_error(self) - - @classmethod - def from_string(cls, key, key_id=None): - """Construct an Signer instance from a private key in PEM format. - - Args: - key (str): Private key in PEM format. - key_id (str): An optional key id used to identify the private key. - - Returns: - google.auth.crypt.Signer: The constructed signer. - - Raises: - ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in - PEM format. - """ - raise _build_error(cls) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 62864b052..63deaf232 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -12,26 +12,146 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""RSA cryptography signer and verifier.""" +""" +RSA cryptography signer and verifier. +This file provides a shared wrapper, that defers to _python_rsa or _cryptography_rsa +for implmentations using different third party libraries +""" try: - # Prefer cryptograph-based RSA implementation. + # Attempt import of module that requires optional `cryptography` dependency from google.auth.crypt import _cryptography_rsa +except ImportError: # pragma: NO COVER + _cryptography_rsa = None - RSASigner = _cryptography_rsa.RSASigner - RSAVerifier = _cryptography_rsa.RSAVerifier +try: + # Attempt import of module that requires optional (deprecated) `rsa` dependency + from google.auth.crypt import _python_rsa except ImportError: # pragma: NO COVER - try: - # Try pure-python RSA implementation if cryptography is - # unavailable. - from google.auth.crypt import _python_rsa - - RSASigner = _python_rsa.RSASigner # type: ignore - RSAVerifier = _python_rsa.RSAVerifier # type: ignore - except ImportError: # pragma: NO COVER - # if rsa is not available, use default implementation (raises ImportError on use) - from google.auth.crypt import _default_rsa - - RSASigner = _default_rsa.RSASigner # type: ignore - RSAVerifier = _default_rsa.RSAVerifier # type: ignore + _python_rsa = None + + +def _missing_impl_error(obj_or_cls): + """ + If the user has neither `cryptography` or `rsa` installed, raise an ImportError + """ + cls = obj_or_cls if isinstance(obj_or_cls, type) else type(obj_or_cls) + return ImportError( + ( + f"{cls.__name__} requires `cryptography` optional dependency.", + "(Note: 'rsa' is also supported for legacy compatibility but is deprecated).", + ) + ) + +class RSAVerifier(base.Verifier): + """Verifies RSA cryptographic signatures using public keys. + + Args: + public_key (Union[rsa.key.PublicKey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey]): + The public key used to verify signatures. + Raises: + ImportError: if neither `cryptograhy` or `rsa` is installed + InvalidValue: if an unrecognized public key is provided + """ + + def __init__(self, public_key): + module_str = private_key.__class__.__module__ + if "rsa.key" in module_str: + impl_lib = _python_rsa + elif "cryptography." in module_str: + impl_lib = _cryptography_rsa + else: + raise InvalidValue(f"unrecognized public key type: {public_key}") + if impl_lib is None: + raise _missing_impl_error(self) + else: + self._impl = impl_lib.RSAVerifier(public_key) + + @_helpers.copy_docstring(base.Verifier) + def verify(self, message, signature): + return self._impl(message, signature) + + @classmethod + def from_string(cls, public_key): + """Construct an Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + google.auth.crypt.RSAVerifier: The constructed verifier. + + Raises: + ValueError: If the public_key can't be parsed. + ImportError: if neither `cryptograhy` or `rsa` is installe + """ + if _cryptography_rsa: + return _cryptography_rsa.RSAVerifier.from_string(public_key) + elif _python_rsa: + return _python_rsa.RSAVerifier.from_string(public_key) + else: + raise _missing_impl_error(cls) + + +class RSASigner(base.Signer, base.FromServiceAccountMixin): + """Signs messages with an RSA private key. + + Args: + private_key (Union[rsa.key.PrivateKey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey]): + The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + + Raises: + ImportError: if neither `cryptograhy` or `rsa` is installed + InvalidValue: if an unrecognized public key is provided + """ + + def __init__(self, private_key, key_id=None): + module_str = private_key.__class__.__module__ + if "rsa.key" in module_str: + impl_lib = _python_rsa + elif "cryptography." in module_str: + impl_lib = _cryptography_rsa + else: + raise InvalidValue(f"unrecognized public key type: {public_key}") + if impl_lib is None: + raise _missing_impl_error(self) + else: + self._impl = impl_lib.RSASigner(private_key, key_id=key_id) + + @property # type: ignore + @_helpers.copy_docstring(base.Signer) + def key_id(self): + return self._impl.key_id() + + @_helpers.copy_docstring(base.Signer) + def sign(self, message): + return self._impl.sign(message) + + @classmethod + def from_string(cls, key, key_id=None): + """Construct an Signer instance from a private key in PEM format. + + Args: + key (str): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt.Signer: The constructed signer. + + Raises: + ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in + PEM format. + ImportError: if neither `cryptograhy` or `rsa` is installe + """ + if _cryptography_rsa: + return _cryptography_rsa.RSAVerifier.from_string(public_key) + elif _python_rsa: + return _python_rsa.RSAVerifier.from_string(public_key) + else: + raise _missing_impl_error(cls) \ No newline at end of file From ad651ed48bd352095776d26f8d35a8b2c1ba198b Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 15:33:35 -0800 Subject: [PATCH 11/50] changed warning type --- google/auth/crypt/_python_rsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/auth/crypt/_python_rsa.py b/google/auth/crypt/_python_rsa.py index 64ca958eb..c4b35c5ce 100644 --- a/google/auth/crypt/_python_rsa.py +++ b/google/auth/crypt/_python_rsa.py @@ -45,7 +45,7 @@ "The 'rsa' library is deprecated and will be removed in a future release. " "Please migrate to 'cryptography'." ), - category=FutureWarning, + category=DeprecationWarning, stacklevel=2, ) From 540f260f0fd32b67ac71946ecadc944b4060a847 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 15:35:31 -0800 Subject: [PATCH 12/50] added deprecation notices to docstrings --- google/auth/crypt/rsa.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 63deaf232..88ed432f5 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -47,6 +47,10 @@ def _missing_impl_error(obj_or_cls): class RSAVerifier(base.Verifier): """Verifies RSA cryptographic signatures using public keys. + .. deprecated:: + The `rsa` library has been archived. Please migrate to + `cryptography` for public keys. + Args: public_key (Union[rsa.key.PublicKey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey]): The public key used to verify signatures. @@ -99,6 +103,10 @@ def from_string(cls, public_key): class RSASigner(base.Signer, base.FromServiceAccountMixin): """Signs messages with an RSA private key. + .. deprecated:: + The `rsa` library has been archived. Please migrate to + `cryptography` for public keys. + Args: private_key (Union[rsa.key.PrivateKey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey]): The private key to sign with. From 68410f1d2330f6ff1d54893eea0c166075ffb06f Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 15:40:15 -0800 Subject: [PATCH 13/50] moved warning --- google/auth/crypt/_python_rsa.py | 21 +++++++++++++-------- tests/crypt/test__python_rsa.py | 4 +++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/google/auth/crypt/_python_rsa.py b/google/auth/crypt/_python_rsa.py index c4b35c5ce..232a909c6 100644 --- a/google/auth/crypt/_python_rsa.py +++ b/google/auth/crypt/_python_rsa.py @@ -40,16 +40,11 @@ _PKCS8_MARKER = ("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----") _PKCS8_SPEC = PrivateKeyInfo() -warnings.warn( - ( - "The 'rsa' library is deprecated and will be removed in a future release. " - "Please migrate to 'cryptography'." - ), - category=DeprecationWarning, - stacklevel=2, +_warning_msg = ( + "The 'rsa' library is deprecated and will be removed in a future release. " + "Please migrate to 'cryptography'." ) - def _bit_list_to_bytes(bit_list): """Converts an iterable of 1s and 0s to bytes. @@ -84,6 +79,11 @@ class RSAVerifier(base.Verifier): """ def __init__(self, public_key): + warnings.warn( + _warning_msg, + category=DeprecationWarning, + stacklevel=2, + ) self._pubkey = public_key @_helpers.copy_docstring(base.Verifier) @@ -142,6 +142,11 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): """ def __init__(self, private_key, key_id=None): + warnings.warn( + _warning_msg, + category=DeprecationWarning, + stacklevel=2, + ) self._key = private_key self._key_id = key_id diff --git a/tests/crypt/test__python_rsa.py b/tests/crypt/test__python_rsa.py index 6eb09472a..ab4fbc930 100644 --- a/tests/crypt/test__python_rsa.py +++ b/tests/crypt/test__python_rsa.py @@ -199,4 +199,6 @@ def test_import_warning(self): from google.auth.crypt import _python_rsa with pytest.warns(FutureWarning, match="The 'rsa' library is deprecated"): - importlib.reload(_python_rsa) + _python_rsa.RSAVerifier(None) + with pytest.warns(FutureWarning, match="The 'rsa' library is deprecated"): + _python_rsa.RSASigner(None) \ No newline at end of file From f89e44488c22c5786f149960eaaa2b004f77313a Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 15:45:11 -0800 Subject: [PATCH 14/50] fixed warning type in tests --- tests/crypt/test__python_rsa.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/crypt/test__python_rsa.py b/tests/crypt/test__python_rsa.py index ab4fbc930..0165b8834 100644 --- a/tests/crypt/test__python_rsa.py +++ b/tests/crypt/test__python_rsa.py @@ -198,7 +198,7 @@ def test_import_warning(self): import importlib from google.auth.crypt import _python_rsa - with pytest.warns(FutureWarning, match="The 'rsa' library is deprecated"): + with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): _python_rsa.RSAVerifier(None) - with pytest.warns(FutureWarning, match="The 'rsa' library is deprecated"): + with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): _python_rsa.RSASigner(None) \ No newline at end of file From fd429f32a78a0c0cf0ca0c56c9d9a60cc18a6b62 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 15:50:23 -0800 Subject: [PATCH 15/50] remove rsa as required dependency --- setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 74036339a..0123e02ab 100644 --- a/setup.py +++ b/setup.py @@ -21,15 +21,15 @@ DEPENDENCIES = ( "pyasn1-modules>=0.2.1", - # rsa==4.5 is the last version to support 2.7 - # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 - "rsa>=3.1.4,<5", ) cryptography_base_require = [ "cryptography >= 38.0.3", ] +# TODO: rsa is archived. Remove optional dependency in future release +rsa_extra_require = ["rsa>=3.1.4,<5"] + requests_extra_require = ["requests >= 2.20.0, < 3.0.0"] aiohttp_extra_require = ["aiohttp >= 3.6.2, < 4.0.0", *requests_extra_require] @@ -73,10 +73,12 @@ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1722): `test_aiohttp_requests` depend on # aiohttp < 3.10.0 which is a bug. Investigate and remove the pinned aiohttp version. "aiohttp < 3.10.0", + *rsa_extra_require, ] extras = { "cryptography": cryptography_base_require, + "rsa": rsa_extra_require, "aiohttp": aiohttp_extra_require, "enterprise_cert": enterprise_cert_extra_require, "pyopenssl": pyopenssl_extra_require, From fd1ae50093ac6d5f339f07aef54dd403bd1f9a7a Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 16:33:16 -0800 Subject: [PATCH 16/50] fixed errors --- google/auth/crypt/rsa.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 88ed432f5..aaab5be4e 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -19,6 +19,8 @@ for implmentations using different third party libraries """ +from google.auth.crypt import base +from google.auth import _helpers try: # Attempt import of module that requires optional `cryptography` dependency from google.auth.crypt import _cryptography_rsa @@ -47,6 +49,8 @@ def _missing_impl_error(obj_or_cls): class RSAVerifier(base.Verifier): """Verifies RSA cryptographic signatures using public keys. + Requires installation of `cryptography` optional dependency. + .. deprecated:: The `rsa` library has been archived. Please migrate to `cryptography` for public keys. @@ -103,6 +107,8 @@ def from_string(cls, public_key): class RSASigner(base.Signer, base.FromServiceAccountMixin): """Signs messages with an RSA private key. + Requires installation of `cryptography` optional dependency. + .. deprecated:: The `rsa` library has been archived. Please migrate to `cryptography` for public keys. @@ -158,8 +164,8 @@ def from_string(cls, key, key_id=None): ImportError: if neither `cryptograhy` or `rsa` is installe """ if _cryptography_rsa: - return _cryptography_rsa.RSAVerifier.from_string(public_key) + return _cryptography_rsa.RSASigner.from_string(key, key_id=key_id) elif _python_rsa: - return _python_rsa.RSAVerifier.from_string(public_key) + return _python_rsa.RSASigner.from_string(key, key_id=key_id) else: raise _missing_impl_error(cls) \ No newline at end of file From 08959980bbd6ca985e11ec3b3fcf137884f90132 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 16:39:48 -0800 Subject: [PATCH 17/50] added custom exception --- google/auth/crypt/rsa.py | 21 ++++++--------------- google/auth/exceptions.py | 12 ++++++++++++ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index aaab5be4e..9337d96e9 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -21,6 +21,7 @@ from google.auth.crypt import base from google.auth import _helpers +from google.auth.exceptions import MissingOptionalDependencyError try: # Attempt import of module that requires optional `cryptography` dependency from google.auth.crypt import _cryptography_rsa @@ -33,18 +34,8 @@ except ImportError: # pragma: NO COVER _python_rsa = None +RSA_NOTE = "(Note: 'rsa' is also supported for legacy compatibility but is deprecated)", -def _missing_impl_error(obj_or_cls): - """ - If the user has neither `cryptography` or `rsa` installed, raise an ImportError - """ - cls = obj_or_cls if isinstance(obj_or_cls, type) else type(obj_or_cls) - return ImportError( - ( - f"{cls.__name__} requires `cryptography` optional dependency.", - "(Note: 'rsa' is also supported for legacy compatibility but is deprecated).", - ) - ) class RSAVerifier(base.Verifier): """Verifies RSA cryptographic signatures using public keys. @@ -72,7 +63,7 @@ def __init__(self, public_key): else: raise InvalidValue(f"unrecognized public key type: {public_key}") if impl_lib is None: - raise _missing_impl_error(self) + raise MissingOptionalDependencyError.create(self, "cryptography", RSA_NOTE) else: self._impl = impl_lib.RSAVerifier(public_key) @@ -101,7 +92,7 @@ def from_string(cls, public_key): elif _python_rsa: return _python_rsa.RSAVerifier.from_string(public_key) else: - raise _missing_impl_error(cls) + raise MissingOptionalDependencyError.create(cls, "cryptography", RSA_NOTE) class RSASigner(base.Signer, base.FromServiceAccountMixin): @@ -134,7 +125,7 @@ def __init__(self, private_key, key_id=None): else: raise InvalidValue(f"unrecognized public key type: {public_key}") if impl_lib is None: - raise _missing_impl_error(self) + raise MissingOptionalDependencyError.create(self, "cryptography", RSA_NOTE) else: self._impl = impl_lib.RSASigner(private_key, key_id=key_id) @@ -168,4 +159,4 @@ def from_string(cls, key, key_id=None): elif _python_rsa: return _python_rsa.RSASigner.from_string(key, key_id=key_id) else: - raise _missing_impl_error(cls) \ No newline at end of file + raise MissingOptionalDependencyError.create(cls, "cryptography", RSA_NOTE) \ No newline at end of file diff --git a/google/auth/exceptions.py b/google/auth/exceptions.py index feb9f7411..11c7d5b6c 100644 --- a/google/auth/exceptions.py +++ b/google/auth/exceptions.py @@ -106,3 +106,15 @@ class TimeoutError(GoogleAuthError): class ResponseError(GoogleAuthError): """Used to indicate an error occurred when reading an HTTP response.""" + +class MissingOptionalDependencyError(ImportError): + """Raised when a user attempts to use a class that requires an optional dependency""" + + @classmethod + def create(cls, caller, dependency_name, suffix_str=None): + """Creates an instance referencing the required dependency and the triggering class""" + caller_cls = caller if isinstance(caller, type) else type(caller) + msg_str = f"{caller_cls.__name__} requires `{dependency_name}` optional dependency." + if suffix_str: + msg_str = f"{msg_str} {suffix_str}" + return cls(msg_str) \ No newline at end of file From 69bae96056e6d2af586e8f0b3f708efabe24ca36 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 16:46:13 -0800 Subject: [PATCH 18/50] ran blacken --- google/auth/crypt/_python_rsa.py | 3 ++- google/auth/crypt/rsa.py | 3 ++- google/auth/exceptions.py | 7 +++++-- setup.py | 4 +--- tests/crypt/test__python_rsa.py | 2 +- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/google/auth/crypt/_python_rsa.py b/google/auth/crypt/_python_rsa.py index 232a909c6..d9305e835 100644 --- a/google/auth/crypt/_python_rsa.py +++ b/google/auth/crypt/_python_rsa.py @@ -40,11 +40,12 @@ _PKCS8_MARKER = ("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----") _PKCS8_SPEC = PrivateKeyInfo() -_warning_msg = ( +_warning_msg = ( "The 'rsa' library is deprecated and will be removed in a future release. " "Please migrate to 'cryptography'." ) + def _bit_list_to_bytes(bit_list): """Converts an iterable of 1s and 0s to bytes. diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 9337d96e9..90e4dd2dd 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -22,6 +22,7 @@ from google.auth.crypt import base from google.auth import _helpers from google.auth.exceptions import MissingOptionalDependencyError + try: # Attempt import of module that requires optional `cryptography` dependency from google.auth.crypt import _cryptography_rsa @@ -34,7 +35,7 @@ except ImportError: # pragma: NO COVER _python_rsa = None -RSA_NOTE = "(Note: 'rsa' is also supported for legacy compatibility but is deprecated)", +RSA_NOTE = "(Note: 'rsa' is also supported for legacy compatibility but is deprecated)" class RSAVerifier(base.Verifier): diff --git a/google/auth/exceptions.py b/google/auth/exceptions.py index 11c7d5b6c..8dbce2bc1 100644 --- a/google/auth/exceptions.py +++ b/google/auth/exceptions.py @@ -107,6 +107,7 @@ class TimeoutError(GoogleAuthError): class ResponseError(GoogleAuthError): """Used to indicate an error occurred when reading an HTTP response.""" + class MissingOptionalDependencyError(ImportError): """Raised when a user attempts to use a class that requires an optional dependency""" @@ -114,7 +115,9 @@ class MissingOptionalDependencyError(ImportError): def create(cls, caller, dependency_name, suffix_str=None): """Creates an instance referencing the required dependency and the triggering class""" caller_cls = caller if isinstance(caller, type) else type(caller) - msg_str = f"{caller_cls.__name__} requires `{dependency_name}` optional dependency." + msg_str = ( + f"{caller_cls.__name__} requires `{dependency_name}` optional dependency." + ) if suffix_str: msg_str = f"{msg_str} {suffix_str}" - return cls(msg_str) \ No newline at end of file + return cls(msg_str) diff --git a/setup.py b/setup.py index 0123e02ab..6c3d22102 100644 --- a/setup.py +++ b/setup.py @@ -19,9 +19,7 @@ from setuptools import setup -DEPENDENCIES = ( - "pyasn1-modules>=0.2.1", -) +DEPENDENCIES = ("pyasn1-modules>=0.2.1",) cryptography_base_require = [ "cryptography >= 38.0.3", diff --git a/tests/crypt/test__python_rsa.py b/tests/crypt/test__python_rsa.py index 0165b8834..18c8b742d 100644 --- a/tests/crypt/test__python_rsa.py +++ b/tests/crypt/test__python_rsa.py @@ -201,4 +201,4 @@ def test_import_warning(self): with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): _python_rsa.RSAVerifier(None) with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): - _python_rsa.RSASigner(None) \ No newline at end of file + _python_rsa.RSASigner(None) From a8031988f5e797946f99202adaeeb6ca7f91dcb3 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 16:56:46 -0800 Subject: [PATCH 19/50] added new test file --- tests/crypt/test_rsa.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/crypt/test_rsa.py diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py new file mode 100644 index 000000000..e69de29bb From 716bec3338be4ded6fbaa371f05b668238d958b6 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 16:57:02 -0800 Subject: [PATCH 20/50] added test file --- tests/crypt/test_rsa.py | 185 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index e69de29bb..a2a55f097 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -0,0 +1,185 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import pytest +from unittest import mock + +from google.auth import exceptions +from google.auth.crypt import rsa +from google.auth.crypt import _python_rsa +try: + from google.auth.crypt import _cryptography_rsa +except ImportError: + _cryptography_rsa = None + +# Mock objects to simulate keys from different libraries +class MockRsaPublicKey: + pass + +class MockCryptographyPublicKey: + pass + +class MockRsaPrivateKey: + pass + +class MockCryptographyPrivateKey: + pass + +# We need to set the module attributes to match what the code expects +MockRsaPublicKey.__module__ = "rsa.key" +MockCryptographyPublicKey.__module__ = "cryptography.hazmat.primitives.asymmetric.rsa" +MockRsaPrivateKey.__module__ = "rsa.key" +MockCryptographyPrivateKey.__module__ = "cryptography.hazmat.primitives.asymmetric.rsa" + +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") + +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: + PRIVATE_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, "privatekey.pub"), "rb") as fh: + PUBLIC_KEY_BYTES = fh.read() + +class TestRSAVerifier: + def test_init_with_cryptography_key(self): + if _cryptography_rsa is None: + pytest.skip("Cryptography not installed") + + pub_key = MockCryptographyPublicKey() + verifier = rsa.RSAVerifier(pub_key) + assert isinstance(verifier._impl, _cryptography_rsa.RSAVerifier) + assert verifier._impl._pubkey == pub_key + + def test_init_with_rsa_key(self): + pub_key = MockRsaPublicKey() + verifier = rsa.RSAVerifier(pub_key) + assert isinstance(verifier._impl, _python_rsa.RSAVerifier) + assert verifier._impl._pubkey == pub_key + + def test_init_with_unknown_key(self): + pub_key = "not a key" + # The code attempts to access __class__.__module__ which str has, + # but it won't match. + # However, the current code has a bug (uses private_key instead of public_key) + # so this might raise NameError or similar before InvalidValue. + # We expect InvalidValue eventually. + with pytest.raises((exceptions.InvalidValue, NameError)): + # Allowing NameError temporarily to catch the bug existence if tested + rsa.RSAVerifier(pub_key) + + def test_verify_delegates(self): + if _cryptography_rsa is None: + pytest.skip("Cryptography not installed") + + pub_key = MockCryptographyPublicKey() + verifier = rsa.RSAVerifier(pub_key) + + # Mock the implementation's verify method + with mock.patch.object(verifier._impl, "verify", return_value=True) as mock_verify: + result = verifier.verify(b"message", b"signature") + assert result is True + mock_verify.assert_called_once_with(b"message", b"signature") + + @mock.patch("google.auth.crypt.rsa._cryptography_rsa") + def test_from_string_delegates_to_cryptography(self, mock_crypto): + # Setup mock to return a dummy verifier + expected_verifier = mock.Mock() + mock_crypto.RSAVerifier.from_string.return_value = expected_verifier + + result = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + + assert result == expected_verifier + mock_crypto.RSAVerifier.from_string.assert_called_once_with(PUBLIC_KEY_BYTES) + + @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) + @mock.patch("google.auth.crypt.rsa._python_rsa") + def test_from_string_delegates_to_python_rsa(self, mock_python_rsa): + expected_verifier = mock.Mock() + mock_python_rsa.RSAVerifier.from_string.return_value = expected_verifier + + result = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + + assert result == expected_verifier + mock_python_rsa.RSAVerifier.from_string.assert_called_once_with(PUBLIC_KEY_BYTES) + + @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) + @mock.patch("google.auth.crypt.rsa._python_rsa", None) + def test_from_string_missing_deps(self): + with pytest.raises(exceptions.MissingOptionalDependencyError): + rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + + +class TestRSASigner: + def test_init_with_cryptography_key(self): + if _cryptography_rsa is None: + pytest.skip("Cryptography not installed") + + priv_key = MockCryptographyPrivateKey() + signer = rsa.RSASigner(priv_key, key_id="123") + assert isinstance(signer._impl, _cryptography_rsa.RSASigner) + assert signer._impl._key == priv_key + assert signer._impl.key_id == "123" + + def test_init_with_rsa_key(self): + priv_key = MockRsaPrivateKey() + signer = rsa.RSASigner(priv_key, key_id="123") + assert isinstance(signer._impl, _python_rsa.RSASigner) + assert signer._impl._key == priv_key + assert signer._impl.key_id == "123" + + def test_sign_delegates(self): + if _cryptography_rsa is None: + pytest.skip("Cryptography not installed") + + priv_key = MockCryptographyPrivateKey() + signer = rsa.RSASigner(priv_key) + + with mock.patch.object(signer._impl, "sign", return_value=b"signature") as mock_sign: + result = signer.sign(b"message") + assert result == b"signature" + mock_sign.assert_called_once_with(b"message") + + def test_key_id_delegates(self): + if _cryptography_rsa is None: + pytest.skip("Cryptography not installed") + + priv_key = MockCryptographyPrivateKey() + signer = rsa.RSASigner(priv_key, key_id="my-key-id") + + # rsa.py has a bug where it calls key_id() instead of property access + # We'll test for what it *should* do, or verify the bug exists if we must. + # But generally we should fix the bug. + # Assuming we will fix the bug, we expect: + assert signer.key_id == "my-key-id" + + @mock.patch("google.auth.crypt.rsa._cryptography_rsa") + def test_from_string_delegates_to_cryptography(self, mock_crypto): + expected_signer = mock.Mock() + mock_crypto.RSASigner.from_string.return_value = expected_signer + + result = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES, key_id="123") + + assert result == expected_signer + mock_crypto.RSASigner.from_string.assert_called_once_with(PRIVATE_KEY_BYTES, key_id="123") + + @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) + @mock.patch("google.auth.crypt.rsa._python_rsa") + def test_from_string_delegates_to_python_rsa(self, mock_python_rsa): + expected_signer = mock.Mock() + mock_python_rsa.RSASigner.from_string.return_value = expected_signer + + result = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES, key_id="123") + + assert result == expected_signer + mock_python_rsa.RSASigner.from_string.assert_called_once_with(PRIVATE_KEY_BYTES, key_id="123") From 6191d3bb94a983547b53524d53ece4f1c1a74e44 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 16:57:47 -0800 Subject: [PATCH 21/50] fixed bugs in implementation --- google/auth/crypt/rsa.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 90e4dd2dd..6af4b0c60 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -56,7 +56,7 @@ class RSAVerifier(base.Verifier): """ def __init__(self, public_key): - module_str = private_key.__class__.__module__ + module_str = public_key.__class__.__module__ if "rsa.key" in module_str: impl_lib = _python_rsa elif "cryptography." in module_str: @@ -70,7 +70,7 @@ def __init__(self, public_key): @_helpers.copy_docstring(base.Verifier) def verify(self, message, signature): - return self._impl(message, signature) + return self._impl.verify(message, signature) @classmethod def from_string(cls, public_key): @@ -124,7 +124,7 @@ def __init__(self, private_key, key_id=None): elif "cryptography." in module_str: impl_lib = _cryptography_rsa else: - raise InvalidValue(f"unrecognized public key type: {public_key}") + raise InvalidValue(f"unrecognized private key type: {pivate_key}") if impl_lib is None: raise MissingOptionalDependencyError.create(self, "cryptography", RSA_NOTE) else: @@ -133,7 +133,7 @@ def __init__(self, private_key, key_id=None): @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): - return self._impl.key_id() + return self._impl.key_id @_helpers.copy_docstring(base.Signer) def sign(self, message): From b7c270ba346a0596664e0f40658ecf54410bca3e Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 16:59:36 -0800 Subject: [PATCH 22/50] InvalidValue -> ValueError --- google/auth/crypt/rsa.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 6af4b0c60..5452f4ab1 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -52,7 +52,7 @@ class RSAVerifier(base.Verifier): The public key used to verify signatures. Raises: ImportError: if neither `cryptograhy` or `rsa` is installed - InvalidValue: if an unrecognized public key is provided + ValueError: if an unrecognized public key is provided """ def __init__(self, public_key): @@ -62,7 +62,7 @@ def __init__(self, public_key): elif "cryptography." in module_str: impl_lib = _cryptography_rsa else: - raise InvalidValue(f"unrecognized public key type: {public_key}") + raise ValueError(f"unrecognized public key type: {public_key}") if impl_lib is None: raise MissingOptionalDependencyError.create(self, "cryptography", RSA_NOTE) else: @@ -114,7 +114,7 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): Raises: ImportError: if neither `cryptograhy` or `rsa` is installed - InvalidValue: if an unrecognized public key is provided + ValueError: if an unrecognized public key is provided """ def __init__(self, private_key, key_id=None): @@ -124,7 +124,7 @@ def __init__(self, private_key, key_id=None): elif "cryptography." in module_str: impl_lib = _cryptography_rsa else: - raise InvalidValue(f"unrecognized private key type: {pivate_key}") + raise ValueError(f"unrecognized private key type: {pivate_key}") if impl_lib is None: raise MissingOptionalDependencyError.create(self, "cryptography", RSA_NOTE) else: From a1e0389c4108e367e5885de5295c8d7d404047c5 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 17:03:25 -0800 Subject: [PATCH 23/50] clean up tests --- tests/crypt/test_rsa.py | 48 ++++++++++------------------------------- 1 file changed, 11 insertions(+), 37 deletions(-) diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index a2a55f097..417d7a9ec 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -53,9 +53,6 @@ class MockCryptographyPrivateKey: class TestRSAVerifier: def test_init_with_cryptography_key(self): - if _cryptography_rsa is None: - pytest.skip("Cryptography not installed") - pub_key = MockCryptographyPublicKey() verifier = rsa.RSAVerifier(pub_key) assert isinstance(verifier._impl, _cryptography_rsa.RSAVerifier) @@ -69,22 +66,13 @@ def test_init_with_rsa_key(self): def test_init_with_unknown_key(self): pub_key = "not a key" - # The code attempts to access __class__.__module__ which str has, - # but it won't match. - # However, the current code has a bug (uses private_key instead of public_key) - # so this might raise NameError or similar before InvalidValue. - # We expect InvalidValue eventually. - with pytest.raises((exceptions.InvalidValue, NameError)): - # Allowing NameError temporarily to catch the bug existence if tested + with pytest.raises(ValueError): rsa.RSAVerifier(pub_key) def test_verify_delegates(self): - if _cryptography_rsa is None: - pytest.skip("Cryptography not installed") - pub_key = MockCryptographyPublicKey() verifier = rsa.RSAVerifier(pub_key) - + # Mock the implementation's verify method with mock.patch.object(verifier._impl, "verify", return_value=True) as mock_verify: result = verifier.verify(b"message", b"signature") @@ -96,9 +84,9 @@ def test_from_string_delegates_to_cryptography(self, mock_crypto): # Setup mock to return a dummy verifier expected_verifier = mock.Mock() mock_crypto.RSAVerifier.from_string.return_value = expected_verifier - + result = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) - + assert result == expected_verifier mock_crypto.RSAVerifier.from_string.assert_called_once_with(PUBLIC_KEY_BYTES) @@ -107,9 +95,9 @@ def test_from_string_delegates_to_cryptography(self, mock_crypto): def test_from_string_delegates_to_python_rsa(self, mock_python_rsa): expected_verifier = mock.Mock() mock_python_rsa.RSAVerifier.from_string.return_value = expected_verifier - + result = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) - + assert result == expected_verifier mock_python_rsa.RSAVerifier.from_string.assert_called_once_with(PUBLIC_KEY_BYTES) @@ -122,9 +110,6 @@ def test_from_string_missing_deps(self): class TestRSASigner: def test_init_with_cryptography_key(self): - if _cryptography_rsa is None: - pytest.skip("Cryptography not installed") - priv_key = MockCryptographyPrivateKey() signer = rsa.RSASigner(priv_key, key_id="123") assert isinstance(signer._impl, _cryptography_rsa.RSASigner) @@ -139,37 +124,26 @@ def test_init_with_rsa_key(self): assert signer._impl.key_id == "123" def test_sign_delegates(self): - if _cryptography_rsa is None: - pytest.skip("Cryptography not installed") - priv_key = MockCryptographyPrivateKey() signer = rsa.RSASigner(priv_key) - + with mock.patch.object(signer._impl, "sign", return_value=b"signature") as mock_sign: result = signer.sign(b"message") assert result == b"signature" mock_sign.assert_called_once_with(b"message") def test_key_id_delegates(self): - if _cryptography_rsa is None: - pytest.skip("Cryptography not installed") - priv_key = MockCryptographyPrivateKey() signer = rsa.RSASigner(priv_key, key_id="my-key-id") - - # rsa.py has a bug where it calls key_id() instead of property access - # We'll test for what it *should* do, or verify the bug exists if we must. - # But generally we should fix the bug. - # Assuming we will fix the bug, we expect: assert signer.key_id == "my-key-id" @mock.patch("google.auth.crypt.rsa._cryptography_rsa") def test_from_string_delegates_to_cryptography(self, mock_crypto): expected_signer = mock.Mock() mock_crypto.RSASigner.from_string.return_value = expected_signer - + result = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES, key_id="123") - + assert result == expected_signer mock_crypto.RSASigner.from_string.assert_called_once_with(PRIVATE_KEY_BYTES, key_id="123") @@ -178,8 +152,8 @@ def test_from_string_delegates_to_cryptography(self, mock_crypto): def test_from_string_delegates_to_python_rsa(self, mock_python_rsa): expected_signer = mock.Mock() mock_python_rsa.RSASigner.from_string.return_value = expected_signer - + result = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES, key_id="123") - + assert result == expected_signer mock_python_rsa.RSASigner.from_string.assert_called_once_with(PRIVATE_KEY_BYTES, key_id="123") From 6b17faad1971120a6ced2b8357bcd235971e68ea Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 17:34:56 -0800 Subject: [PATCH 24/50] finished tests --- google/auth/crypt/rsa.py | 4 +- tests/crypt/test_rsa.py | 113 ++++++++++++++++++++++----------------- 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 5452f4ab1..07e0c0381 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -35,7 +35,7 @@ except ImportError: # pragma: NO COVER _python_rsa = None -RSA_NOTE = "(Note: 'rsa' is also supported for legacy compatibility but is deprecated)" +RSA_NOTE = "(Note: `rsa` is also supported for legacy compatibility, but is deprecated)" class RSAVerifier(base.Verifier): @@ -124,7 +124,7 @@ def __init__(self, private_key, key_id=None): elif "cryptography." in module_str: impl_lib = _cryptography_rsa else: - raise ValueError(f"unrecognized private key type: {pivate_key}") + raise ValueError(f"unrecognized private key type: {private_key}") if impl_lib is None: raise MissingOptionalDependencyError.create(self, "cryptography", RSA_NOTE) else: diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index 417d7a9ec..814de1527 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -16,62 +16,59 @@ import pytest from unittest import mock +import rsa as rsa_lib +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat import backends + from google.auth import exceptions from google.auth.crypt import rsa from google.auth.crypt import _python_rsa -try: - from google.auth.crypt import _cryptography_rsa -except ImportError: - _cryptography_rsa = None - -# Mock objects to simulate keys from different libraries -class MockRsaPublicKey: - pass - -class MockCryptographyPublicKey: - pass - -class MockRsaPrivateKey: - pass +from google.auth.crypt import _cryptography_rsa -class MockCryptographyPrivateKey: - pass - -# We need to set the module attributes to match what the code expects -MockRsaPublicKey.__module__ = "rsa.key" -MockCryptographyPublicKey.__module__ = "cryptography.hazmat.primitives.asymmetric.rsa" -MockRsaPrivateKey.__module__ = "rsa.key" -MockCryptographyPrivateKey.__module__ = "cryptography.hazmat.primitives.asymmetric.rsa" DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: PRIVATE_KEY_BYTES = fh.read() + CRYPTOGRAPHY_PRIVATE_KEY = serialization.load_pem_private_key( + PRIVATE_KEY_BYTES, password=None, backend=backends.default_backend() + ) + RSA_PRIVATE_KEY = rsa_lib.PrivateKey.load_pkcs1(PRIVATE_KEY_BYTES) with open(os.path.join(DATA_DIR, "privatekey.pub"), "rb") as fh: PUBLIC_KEY_BYTES = fh.read() + CRYPTOGRAPHY_PUBLIC_KEY = serialization.load_pem_public_key( + PUBLIC_KEY_BYTES, backend=backends.default_backend() + ) + RSA_PUBLIC_KEY = rsa_lib.PublicKey.load_pkcs1(PUBLIC_KEY_BYTES) class TestRSAVerifier: def test_init_with_cryptography_key(self): - pub_key = MockCryptographyPublicKey() - verifier = rsa.RSAVerifier(pub_key) + verifier = rsa.RSAVerifier(CRYPTOGRAPHY_PUBLIC_KEY) assert isinstance(verifier._impl, _cryptography_rsa.RSAVerifier) - assert verifier._impl._pubkey == pub_key + assert verifier._impl._pubkey == CRYPTOGRAPHY_PUBLIC_KEY def test_init_with_rsa_key(self): - pub_key = MockRsaPublicKey() - verifier = rsa.RSAVerifier(pub_key) + verifier = rsa.RSAVerifier(RSA_PUBLIC_KEY) assert isinstance(verifier._impl, _python_rsa.RSAVerifier) - assert verifier._impl._pubkey == pub_key + assert verifier._impl._pubkey == RSA_PUBLIC_KEY def test_init_with_unknown_key(self): - pub_key = "not a key" + unknown_key = object() + with pytest.raises(ValueError): - rsa.RSAVerifier(pub_key) + rsa.RSAVerifier(unknown_key) + + @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) + @mock.patch("google.auth.crypt.rsa._python_rsa", None) + def test_init_with_missing_dependencies(self): + with pytest.raises(exceptions.MissingOptionalDependencyError) as e: + rsa.RSAVerifier(RSA_PUBLIC_KEY) + assert "RSAVerifier requires `cryptography`" in str(e) + assert "`rsa` is also supported for legacy compatibility, but is deprecated" in str(e) def test_verify_delegates(self): - pub_key = MockCryptographyPublicKey() - verifier = rsa.RSAVerifier(pub_key) + verifier = rsa.RSAVerifier(CRYPTOGRAPHY_PUBLIC_KEY) # Mock the implementation's verify method with mock.patch.object(verifier._impl, "verify", return_value=True) as mock_verify: @@ -80,8 +77,8 @@ def test_verify_delegates(self): mock_verify.assert_called_once_with(b"message", b"signature") @mock.patch("google.auth.crypt.rsa._cryptography_rsa") - def test_from_string_delegates_to_cryptography(self, mock_crypto): - # Setup mock to return a dummy verifier + @mock.patch("google.auth.crypt.rsa._python_rsa", None) + def test_from_string_cryptography(self, mock_crypto): expected_verifier = mock.Mock() mock_crypto.RSAVerifier.from_string.return_value = expected_verifier @@ -92,7 +89,7 @@ def test_from_string_delegates_to_cryptography(self, mock_crypto): @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) @mock.patch("google.auth.crypt.rsa._python_rsa") - def test_from_string_delegates_to_python_rsa(self, mock_python_rsa): + def test_from_string_python_rsa(self, mock_python_rsa): expected_verifier = mock.Mock() mock_python_rsa.RSAVerifier.from_string.return_value = expected_verifier @@ -104,40 +101,48 @@ def test_from_string_delegates_to_python_rsa(self, mock_python_rsa): @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) @mock.patch("google.auth.crypt.rsa._python_rsa", None) def test_from_string_missing_deps(self): - with pytest.raises(exceptions.MissingOptionalDependencyError): + with pytest.raises(exceptions.MissingOptionalDependencyError) as e: rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) - + assert "RSAVerifier requires `cryptography`" in str(e) + assert "`rsa` is also supported for legacy compatibility, but is deprecated" in str(e) class TestRSASigner: def test_init_with_cryptography_key(self): - priv_key = MockCryptographyPrivateKey() - signer = rsa.RSASigner(priv_key, key_id="123") + signer = rsa.RSASigner(CRYPTOGRAPHY_PRIVATE_KEY, key_id="123") assert isinstance(signer._impl, _cryptography_rsa.RSASigner) - assert signer._impl._key == priv_key + assert signer._impl._key == CRYPTOGRAPHY_PRIVATE_KEY assert signer._impl.key_id == "123" def test_init_with_rsa_key(self): - priv_key = MockRsaPrivateKey() - signer = rsa.RSASigner(priv_key, key_id="123") + signer = rsa.RSASigner(RSA_PRIVATE_KEY, key_id="123") assert isinstance(signer._impl, _python_rsa.RSASigner) - assert signer._impl._key == priv_key + assert signer._impl._key == RSA_PRIVATE_KEY assert signer._impl.key_id == "123" + def test_init_with_unknown_key(self): + unknown_key = object() + + with pytest.raises(ValueError): + rsa.RSASigner(unknown_key) + + @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) + @mock.patch("google.auth.crypt.rsa._python_rsa", None) + def test_init_with_missing_dependencies(self): + with pytest.raises(exceptions.MissingOptionalDependencyError) as e: + rsa.RSASigner(RSA_PRIVATE_KEY) + assert "RSASigner requires `cryptography`" in str(e) + assert "`rsa` is also supported for legacy compatibility, but is deprecated" in str(e) + def test_sign_delegates(self): - priv_key = MockCryptographyPrivateKey() - signer = rsa.RSASigner(priv_key) + signer = rsa.RSASigner(RSA_PRIVATE_KEY) with mock.patch.object(signer._impl, "sign", return_value=b"signature") as mock_sign: result = signer.sign(b"message") assert result == b"signature" mock_sign.assert_called_once_with(b"message") - def test_key_id_delegates(self): - priv_key = MockCryptographyPrivateKey() - signer = rsa.RSASigner(priv_key, key_id="my-key-id") - assert signer.key_id == "my-key-id" - @mock.patch("google.auth.crypt.rsa._cryptography_rsa") + @mock.patch("google.auth.crypt.rsa._python_rsa", None) def test_from_string_delegates_to_cryptography(self, mock_crypto): expected_signer = mock.Mock() mock_crypto.RSASigner.from_string.return_value = expected_signer @@ -157,3 +162,11 @@ def test_from_string_delegates_to_python_rsa(self, mock_python_rsa): assert result == expected_signer mock_python_rsa.RSASigner.from_string.assert_called_once_with(PRIVATE_KEY_BYTES, key_id="123") + + @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) + @mock.patch("google.auth.crypt.rsa._python_rsa", None) + def test_from_string_missing_deps(self): + with pytest.raises(exceptions.MissingOptionalDependencyError) as e: + rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) + assert "RSASigner requires `cryptography`" in str(e) + assert "`rsa` is also supported for legacy compatibility, but is deprecated" in str(e) \ No newline at end of file From e88fc1c0e3f3fa07dbce387f4b2a986e893a47c4 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 17:52:10 -0800 Subject: [PATCH 25/50] added e2e tests --- tests/crypt/test_rsa.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index 814de1527..042296e7e 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -169,4 +169,28 @@ def test_from_string_missing_deps(self): with pytest.raises(exceptions.MissingOptionalDependencyError) as e: rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) assert "RSASigner requires `cryptography`" in str(e) - assert "`rsa` is also supported for legacy compatibility, but is deprecated" in str(e) \ No newline at end of file + assert "`rsa` is also supported for legacy compatibility, but is deprecated" in str(e) + + + @mock.patch("google.auth.crypt.rsa._python_rsa", None) + def test_end_to_end_cryptography_lib(self): + signer = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) + message = b"Hello World" + sig = signer.sign(message) + verifier = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + result = verifier.verify(message, sig) + assert result is True + assert isinstance(verifier, _cryptography_rsa.RSAVerifier) + assert isinstance(signer, _cryptography_rsa.RSASigner) + + + @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) + def test_end_to_end_rsa_lib(self): + signer = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) + message = b"Hello World" + sig = signer.sign(message) + verifier = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + result = verifier.verify(message, sig) + assert bool(result) is True + assert isinstance(verifier, _python_rsa.RSAVerifier) + assert isinstance(signer, _python_rsa.RSASigner) \ No newline at end of file From d7a3b23c9babbd5e159c3df2c5d22c48c2ed17b7 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 17:52:18 -0800 Subject: [PATCH 26/50] added warning tests --- tests/crypt/test_rsa.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index 042296e7e..96707d639 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -53,6 +53,10 @@ def test_init_with_rsa_key(self): assert isinstance(verifier._impl, _python_rsa.RSAVerifier) assert verifier._impl._pubkey == RSA_PUBLIC_KEY + def test_warning_with_rsa(self): + with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): + rsa.RSAVerifier(RSA_PUBLIC_KEY) + def test_init_with_unknown_key(self): unknown_key = object() @@ -119,6 +123,10 @@ def test_init_with_rsa_key(self): assert signer._impl._key == RSA_PRIVATE_KEY assert signer._impl.key_id == "123" + def test_warning_with_rsa(self): + with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): + rsa.RSASigner(RSA_PRIVATE_KEY, key_id="123") + def test_init_with_unknown_key(self): unknown_key = object() From db7037c636a7ea73c534ef68c950f4d82075fd29 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 9 Jan 2026 17:59:45 -0800 Subject: [PATCH 27/50] fixed lint --- google/auth/crypt/rsa.py | 4 +-- tests/crypt/test__python_rsa.py | 1 - tests/crypt/test_rsa.py | 62 ++++++++++++++++++++++----------- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 07e0c0381..b315c6661 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -19,8 +19,8 @@ for implmentations using different third party libraries """ -from google.auth.crypt import base from google.auth import _helpers +from google.auth.crypt import base from google.auth.exceptions import MissingOptionalDependencyError try: @@ -160,4 +160,4 @@ def from_string(cls, key, key_id=None): elif _python_rsa: return _python_rsa.RSASigner.from_string(key, key_id=key_id) else: - raise MissingOptionalDependencyError.create(cls, "cryptography", RSA_NOTE) \ No newline at end of file + raise MissingOptionalDependencyError.create(cls, "cryptography", RSA_NOTE) diff --git a/tests/crypt/test__python_rsa.py b/tests/crypt/test__python_rsa.py index 18c8b742d..43539900f 100644 --- a/tests/crypt/test__python_rsa.py +++ b/tests/crypt/test__python_rsa.py @@ -195,7 +195,6 @@ def test_from_service_account_file(self): class TestModule(object): def test_import_warning(self): - import importlib from google.auth.crypt import _python_rsa with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index 96707d639..91cd951d1 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -13,17 +13,17 @@ # limitations under the License. import os -import pytest from unittest import mock -import rsa as rsa_lib -from cryptography.hazmat.primitives import serialization from cryptography.hazmat import backends +from cryptography.hazmat.primitives import serialization +import pytest +import rsa as rsa_lib from google.auth import exceptions -from google.auth.crypt import rsa -from google.auth.crypt import _python_rsa from google.auth.crypt import _cryptography_rsa +from google.auth.crypt import _python_rsa +from google.auth.crypt import rsa DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") @@ -42,6 +42,7 @@ ) RSA_PUBLIC_KEY = rsa_lib.PublicKey.load_pkcs1(PUBLIC_KEY_BYTES) + class TestRSAVerifier: def test_init_with_cryptography_key(self): verifier = rsa.RSAVerifier(CRYPTOGRAPHY_PUBLIC_KEY) @@ -60,8 +61,8 @@ def test_warning_with_rsa(self): def test_init_with_unknown_key(self): unknown_key = object() - with pytest.raises(ValueError): - rsa.RSAVerifier(unknown_key) + with pytest.raises(ValueError): + rsa.RSAVerifier(unknown_key) @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) @mock.patch("google.auth.crypt.rsa._python_rsa", None) @@ -69,13 +70,18 @@ def test_init_with_missing_dependencies(self): with pytest.raises(exceptions.MissingOptionalDependencyError) as e: rsa.RSAVerifier(RSA_PUBLIC_KEY) assert "RSAVerifier requires `cryptography`" in str(e) - assert "`rsa` is also supported for legacy compatibility, but is deprecated" in str(e) + assert ( + "`rsa` is also supported for legacy compatibility, but is deprecated" + in str(e) + ) def test_verify_delegates(self): verifier = rsa.RSAVerifier(CRYPTOGRAPHY_PUBLIC_KEY) # Mock the implementation's verify method - with mock.patch.object(verifier._impl, "verify", return_value=True) as mock_verify: + with mock.patch.object( + verifier._impl, "verify", return_value=True + ) as mock_verify: result = verifier.verify(b"message", b"signature") assert result is True mock_verify.assert_called_once_with(b"message", b"signature") @@ -100,7 +106,9 @@ def test_from_string_python_rsa(self, mock_python_rsa): result = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert result == expected_verifier - mock_python_rsa.RSAVerifier.from_string.assert_called_once_with(PUBLIC_KEY_BYTES) + mock_python_rsa.RSAVerifier.from_string.assert_called_once_with( + PUBLIC_KEY_BYTES + ) @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) @mock.patch("google.auth.crypt.rsa._python_rsa", None) @@ -108,7 +116,11 @@ def test_from_string_missing_deps(self): with pytest.raises(exceptions.MissingOptionalDependencyError) as e: rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert "RSAVerifier requires `cryptography`" in str(e) - assert "`rsa` is also supported for legacy compatibility, but is deprecated" in str(e) + assert ( + "`rsa` is also supported for legacy compatibility, but is deprecated" + in str(e) + ) + class TestRSASigner: def test_init_with_cryptography_key(self): @@ -131,7 +143,7 @@ def test_init_with_unknown_key(self): unknown_key = object() with pytest.raises(ValueError): - rsa.RSASigner(unknown_key) + rsa.RSASigner(unknown_key) @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) @mock.patch("google.auth.crypt.rsa._python_rsa", None) @@ -139,12 +151,17 @@ def test_init_with_missing_dependencies(self): with pytest.raises(exceptions.MissingOptionalDependencyError) as e: rsa.RSASigner(RSA_PRIVATE_KEY) assert "RSASigner requires `cryptography`" in str(e) - assert "`rsa` is also supported for legacy compatibility, but is deprecated" in str(e) + assert ( + "`rsa` is also supported for legacy compatibility, but is deprecated" + in str(e) + ) def test_sign_delegates(self): signer = rsa.RSASigner(RSA_PRIVATE_KEY) - with mock.patch.object(signer._impl, "sign", return_value=b"signature") as mock_sign: + with mock.patch.object( + signer._impl, "sign", return_value=b"signature" + ) as mock_sign: result = signer.sign(b"message") assert result == b"signature" mock_sign.assert_called_once_with(b"message") @@ -158,7 +175,9 @@ def test_from_string_delegates_to_cryptography(self, mock_crypto): result = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES, key_id="123") assert result == expected_signer - mock_crypto.RSASigner.from_string.assert_called_once_with(PRIVATE_KEY_BYTES, key_id="123") + mock_crypto.RSASigner.from_string.assert_called_once_with( + PRIVATE_KEY_BYTES, key_id="123" + ) @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) @mock.patch("google.auth.crypt.rsa._python_rsa") @@ -169,7 +188,9 @@ def test_from_string_delegates_to_python_rsa(self, mock_python_rsa): result = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES, key_id="123") assert result == expected_signer - mock_python_rsa.RSASigner.from_string.assert_called_once_with(PRIVATE_KEY_BYTES, key_id="123") + mock_python_rsa.RSASigner.from_string.assert_called_once_with( + PRIVATE_KEY_BYTES, key_id="123" + ) @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) @mock.patch("google.auth.crypt.rsa._python_rsa", None) @@ -177,8 +198,10 @@ def test_from_string_missing_deps(self): with pytest.raises(exceptions.MissingOptionalDependencyError) as e: rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) assert "RSASigner requires `cryptography`" in str(e) - assert "`rsa` is also supported for legacy compatibility, but is deprecated" in str(e) - + assert ( + "`rsa` is also supported for legacy compatibility, but is deprecated" + in str(e) + ) @mock.patch("google.auth.crypt.rsa._python_rsa", None) def test_end_to_end_cryptography_lib(self): @@ -191,7 +214,6 @@ def test_end_to_end_cryptography_lib(self): assert isinstance(verifier, _cryptography_rsa.RSAVerifier) assert isinstance(signer, _cryptography_rsa.RSASigner) - @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) def test_end_to_end_rsa_lib(self): signer = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) @@ -201,4 +223,4 @@ def test_end_to_end_rsa_lib(self): result = verifier.verify(message, sig) assert bool(result) is True assert isinstance(verifier, _python_rsa.RSAVerifier) - assert isinstance(signer, _python_rsa.RSASigner) \ No newline at end of file + assert isinstance(signer, _python_rsa.RSASigner) From a9027c4438a2b537fd679fc6e23c9294cae06796 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 12:57:37 -0800 Subject: [PATCH 28/50] fixed typos --- google/auth/crypt/rsa.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index b315c6661..94d4d9595 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -51,7 +51,7 @@ class RSAVerifier(base.Verifier): public_key (Union[rsa.key.PublicKey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey]): The public key used to verify signatures. Raises: - ImportError: if neither `cryptograhy` or `rsa` is installed + ImportError: if neither `cryptography` or `rsa` is installed ValueError: if an unrecognized public key is provided """ @@ -74,7 +74,7 @@ def verify(self, message, signature): @classmethod def from_string(cls, public_key): - """Construct an Verifier instance from a public key or public + """Construct a Verifier instance from a public key or public certificate string. Args: @@ -86,7 +86,7 @@ def from_string(cls, public_key): Raises: ValueError: If the public_key can't be parsed. - ImportError: if neither `cryptograhy` or `rsa` is installe + ImportError: if neither `cryptography` or `rsa` is installe """ if _cryptography_rsa: return _cryptography_rsa.RSAVerifier.from_string(public_key) @@ -113,7 +113,7 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): public key or certificate. Raises: - ImportError: if neither `cryptograhy` or `rsa` is installed + ImportError: if neither `cryptography` or `rsa` is installed ValueError: if an unrecognized public key is provided """ @@ -153,7 +153,7 @@ def from_string(cls, key, key_id=None): Raises: ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in PEM format. - ImportError: if neither `cryptograhy` or `rsa` is installe + ImportError: if neither `cryptography` or `rsa` is installed """ if _cryptography_rsa: return _cryptography_rsa.RSASigner.from_string(key, key_id=key_id) From d6ec87f3581fdb0b249eef8940be89bee81e96d2 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 13:02:01 -0800 Subject: [PATCH 29/50] made cryptography into required dependency --- setup.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 6c3d22102..a8dd78a2d 100644 --- a/setup.py +++ b/setup.py @@ -19,27 +19,30 @@ from setuptools import setup -DEPENDENCIES = ("pyasn1-modules>=0.2.1",) +DEPENDENCIES = ( + "pyasn1-modules>=0.2.1", + "cryptography >= 38.0.3", + # TODO: remove rsa from dependencies in next release (replaced with cryptography) + "rsa>=3.1.4,<5", +) -cryptography_base_require = [ +# Note: cryptography was made into a required dependency. Extra is kept for backwards compatibility +cryptography_extra_require = [ "cryptography >= 38.0.3", ] -# TODO: rsa is archived. Remove optional dependency in future release -rsa_extra_require = ["rsa>=3.1.4,<5"] - requests_extra_require = ["requests >= 2.20.0, < 3.0.0"] aiohttp_extra_require = ["aiohttp >= 3.6.2, < 4.0.0", *requests_extra_require] -pyjwt_extra_require = ["pyjwt>=2.0", *cryptography_base_require] +pyjwt_extra_require = ["pyjwt>=2.0"] reauth_extra_require = ["pyu2f>=0.1.5"] # TODO(https://github.com/googleapis/google-auth-library-python/issues/1738): Add bounds for cryptography and pyopenssl dependencies. enterprise_cert_extra_require = ["cryptography", "pyopenssl"] -pyopenssl_extra_require = ["pyopenssl>=20.0.0", cryptography_base_require] +pyopenssl_extra_require = ["pyopenssl>=20.0.0"] # TODO(https://github.com/googleapis/google-auth-library-python/issues/1739): Add bounds for urllib3 and packaging dependencies. urllib3_extra_require = ["urllib3", "packaging"] @@ -71,12 +74,10 @@ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1722): `test_aiohttp_requests` depend on # aiohttp < 3.10.0 which is a bug. Investigate and remove the pinned aiohttp version. "aiohttp < 3.10.0", - *rsa_extra_require, ] extras = { - "cryptography": cryptography_base_require, - "rsa": rsa_extra_require, + "cryptography": cryptography_extra_require, "aiohttp": aiohttp_extra_require, "enterprise_cert": enterprise_cert_extra_require, "pyopenssl": pyopenssl_extra_require, From 6aa7f41aaa6ad030d740811d4ac01b7b51f43b1c Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 13:17:26 -0800 Subject: [PATCH 30/50] rewrote classes to assume cryptography and rsa are both present --- google/auth/crypt/rsa.py | 68 ++++++++++++++------------------------- google/auth/exceptions.py | 17 +--------- 2 files changed, 26 insertions(+), 59 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 94d4d9595..430b40906 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -22,21 +22,11 @@ from google.auth import _helpers from google.auth.crypt import base from google.auth.exceptions import MissingOptionalDependencyError +from google.auth.crypt import _cryptography_rsa +from google.auth.crypt import _python_rsa -try: - # Attempt import of module that requires optional `cryptography` dependency - from google.auth.crypt import _cryptography_rsa -except ImportError: # pragma: NO COVER - _cryptography_rsa = None - -try: - # Attempt import of module that requires optional (deprecated) `rsa` dependency - from google.auth.crypt import _python_rsa -except ImportError: # pragma: NO COVER - _python_rsa = None - -RSA_NOTE = "(Note: `rsa` is also supported for legacy compatibility, but is deprecated)" - +RSA_KEY_MODULE_PREFIX = "rsa.key" +CRYPTOGRAPHY_KEY_MODULE_PREFIX = "cryptography." class RSAVerifier(base.Verifier): """Verifies RSA cryptographic signatures using public keys. @@ -51,22 +41,18 @@ class RSAVerifier(base.Verifier): public_key (Union[rsa.key.PublicKey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey]): The public key used to verify signatures. Raises: - ImportError: if neither `cryptography` or `rsa` is installed ValueError: if an unrecognized public key is provided """ def __init__(self, public_key): module_str = public_key.__class__.__module__ - if "rsa.key" in module_str: + if module_str.startswith(RSA_KEY_MODULE_PREFIX): impl_lib = _python_rsa - elif "cryptography." in module_str: + elif module_str.startswith(CRYPTOGRAPHY_KEY_MODULE_PREFIX): impl_lib = _cryptography_rsa else: raise ValueError(f"unrecognized public key type: {public_key}") - if impl_lib is None: - raise MissingOptionalDependencyError.create(self, "cryptography", RSA_NOTE) - else: - self._impl = impl_lib.RSAVerifier(public_key) + self._impl = impl_lib.RSAVerifier(public_key) @_helpers.copy_docstring(base.Verifier) def verify(self, message, signature): @@ -86,14 +72,14 @@ def from_string(cls, public_key): Raises: ValueError: If the public_key can't be parsed. - ImportError: if neither `cryptography` or `rsa` is installe """ - if _cryptography_rsa: - return _cryptography_rsa.RSAVerifier.from_string(public_key) - elif _python_rsa: - return _python_rsa.RSAVerifier.from_string(public_key) - else: - raise MissingOptionalDependencyError.create(cls, "cryptography", RSA_NOTE) + try: + instance = cls(None) + except ValueError: + # ignore exception when creating instnce without associated key + pass + instance._impl = _cryptography_rsa.RSAVerifier.from_string(public_key) + return instance class RSASigner(base.Signer, base.FromServiceAccountMixin): @@ -113,22 +99,18 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): public key or certificate. Raises: - ImportError: if neither `cryptography` or `rsa` is installed ValueError: if an unrecognized public key is provided """ def __init__(self, private_key, key_id=None): module_str = private_key.__class__.__module__ - if "rsa.key" in module_str: + if module_str.startswith(RSA_KEY_MODULE_PREFIX): impl_lib = _python_rsa - elif "cryptography." in module_str: - impl_lib = _cryptography_rsa + elif module_str.startswith(CRYPTOGRAPHY_KEY_MODULE_PREFIX): + impl_lib = _cryptography_rs else: raise ValueError(f"unrecognized private key type: {private_key}") - if impl_lib is None: - raise MissingOptionalDependencyError.create(self, "cryptography", RSA_NOTE) - else: - self._impl = impl_lib.RSASigner(private_key, key_id=key_id) + self._impl = impl_lib.RSASigner(private_key, key_id=key_id) @property # type: ignore @_helpers.copy_docstring(base.Signer) @@ -153,11 +135,11 @@ def from_string(cls, key, key_id=None): Raises: ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in PEM format. - ImportError: if neither `cryptography` or `rsa` is installed """ - if _cryptography_rsa: - return _cryptography_rsa.RSASigner.from_string(key, key_id=key_id) - elif _python_rsa: - return _python_rsa.RSASigner.from_string(key, key_id=key_id) - else: - raise MissingOptionalDependencyError.create(cls, "cryptography", RSA_NOTE) + try: + instance = cls(None) + except ValueError: + # ignore exception when creating instnce without associated key + pass + instance._impl = _cryptography_rsa.RSASigner.from_string(key, key_id=key_id) + return instance diff --git a/google/auth/exceptions.py b/google/auth/exceptions.py index 8dbce2bc1..c9b1ee76b 100644 --- a/google/auth/exceptions.py +++ b/google/auth/exceptions.py @@ -105,19 +105,4 @@ class TimeoutError(GoogleAuthError): class ResponseError(GoogleAuthError): - """Used to indicate an error occurred when reading an HTTP response.""" - - -class MissingOptionalDependencyError(ImportError): - """Raised when a user attempts to use a class that requires an optional dependency""" - - @classmethod - def create(cls, caller, dependency_name, suffix_str=None): - """Creates an instance referencing the required dependency and the triggering class""" - caller_cls = caller if isinstance(caller, type) else type(caller) - msg_str = ( - f"{caller_cls.__name__} requires `{dependency_name}` optional dependency." - ) - if suffix_str: - msg_str = f"{msg_str} {suffix_str}" - return cls(msg_str) + """Used to indicate an error occurred when reading an HTTP response.""" \ No newline at end of file From a8efb6b664fea761c2859e9a578b0882a13495f8 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 13:35:36 -0800 Subject: [PATCH 31/50] simplified from_string --- google/auth/crypt/rsa.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 430b40906..dd5ccc7c5 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -21,7 +21,6 @@ from google.auth import _helpers from google.auth.crypt import base -from google.auth.exceptions import MissingOptionalDependencyError from google.auth.crypt import _cryptography_rsa from google.auth.crypt import _python_rsa @@ -68,18 +67,12 @@ def from_string(cls, public_key): x509 public key certificate. Returns: - google.auth.crypt.RSAVerifier: The constructed verifier. + google.auth.crypt.Verifier: The constructed verifier. Raises: ValueError: If the public_key can't be parsed. """ - try: - instance = cls(None) - except ValueError: - # ignore exception when creating instnce without associated key - pass - instance._impl = _cryptography_rsa.RSAVerifier.from_string(public_key) - return instance + return _cryptography_rsa.RSAVerifier.from_string(public_key) class RSASigner(base.Signer, base.FromServiceAccountMixin): @@ -136,10 +129,4 @@ def from_string(cls, key, key_id=None): ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in PEM format. """ - try: - instance = cls(None) - except ValueError: - # ignore exception when creating instnce without associated key - pass - instance._impl = _cryptography_rsa.RSASigner.from_string(key, key_id=key_id) - return instance + return _cryptography_rsa.RSASigner.from_string(key, key_id=key_id) From 02dace88a613fc118461b895ca666dbb61dcfc7f Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 13:44:39 -0800 Subject: [PATCH 32/50] updated tests --- google/auth/crypt/rsa.py | 2 +- tests/crypt/test_rsa.py | 83 ++-------------------------------------- 2 files changed, 5 insertions(+), 80 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index dd5ccc7c5..6fbfdeb97 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -100,7 +100,7 @@ def __init__(self, private_key, key_id=None): if module_str.startswith(RSA_KEY_MODULE_PREFIX): impl_lib = _python_rsa elif module_str.startswith(CRYPTOGRAPHY_KEY_MODULE_PREFIX): - impl_lib = _cryptography_rs + impl_lib = _cryptography_rsa else: raise ValueError(f"unrecognized private key type: {private_key}") self._impl = impl_lib.RSASigner(private_key, key_id=key_id) diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index 91cd951d1..100c02fbc 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -20,7 +20,6 @@ import pytest import rsa as rsa_lib -from google.auth import exceptions from google.auth.crypt import _cryptography_rsa from google.auth.crypt import _python_rsa from google.auth.crypt import rsa @@ -64,17 +63,6 @@ def test_init_with_unknown_key(self): with pytest.raises(ValueError): rsa.RSAVerifier(unknown_key) - @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) - @mock.patch("google.auth.crypt.rsa._python_rsa", None) - def test_init_with_missing_dependencies(self): - with pytest.raises(exceptions.MissingOptionalDependencyError) as e: - rsa.RSAVerifier(RSA_PUBLIC_KEY) - assert "RSAVerifier requires `cryptography`" in str(e) - assert ( - "`rsa` is also supported for legacy compatibility, but is deprecated" - in str(e) - ) - def test_verify_delegates(self): verifier = rsa.RSAVerifier(CRYPTOGRAPHY_PUBLIC_KEY) @@ -87,7 +75,6 @@ def test_verify_delegates(self): mock_verify.assert_called_once_with(b"message", b"signature") @mock.patch("google.auth.crypt.rsa._cryptography_rsa") - @mock.patch("google.auth.crypt.rsa._python_rsa", None) def test_from_string_cryptography(self, mock_crypto): expected_verifier = mock.Mock() mock_crypto.RSAVerifier.from_string.return_value = expected_verifier @@ -97,30 +84,6 @@ def test_from_string_cryptography(self, mock_crypto): assert result == expected_verifier mock_crypto.RSAVerifier.from_string.assert_called_once_with(PUBLIC_KEY_BYTES) - @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) - @mock.patch("google.auth.crypt.rsa._python_rsa") - def test_from_string_python_rsa(self, mock_python_rsa): - expected_verifier = mock.Mock() - mock_python_rsa.RSAVerifier.from_string.return_value = expected_verifier - - result = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) - - assert result == expected_verifier - mock_python_rsa.RSAVerifier.from_string.assert_called_once_with( - PUBLIC_KEY_BYTES - ) - - @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) - @mock.patch("google.auth.crypt.rsa._python_rsa", None) - def test_from_string_missing_deps(self): - with pytest.raises(exceptions.MissingOptionalDependencyError) as e: - rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) - assert "RSAVerifier requires `cryptography`" in str(e) - assert ( - "`rsa` is also supported for legacy compatibility, but is deprecated" - in str(e) - ) - class TestRSASigner: def test_init_with_cryptography_key(self): @@ -145,17 +108,6 @@ def test_init_with_unknown_key(self): with pytest.raises(ValueError): rsa.RSASigner(unknown_key) - @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) - @mock.patch("google.auth.crypt.rsa._python_rsa", None) - def test_init_with_missing_dependencies(self): - with pytest.raises(exceptions.MissingOptionalDependencyError) as e: - rsa.RSASigner(RSA_PRIVATE_KEY) - assert "RSASigner requires `cryptography`" in str(e) - assert ( - "`rsa` is also supported for legacy compatibility, but is deprecated" - in str(e) - ) - def test_sign_delegates(self): signer = rsa.RSASigner(RSA_PRIVATE_KEY) @@ -167,7 +119,6 @@ def test_sign_delegates(self): mock_sign.assert_called_once_with(b"message") @mock.patch("google.auth.crypt.rsa._cryptography_rsa") - @mock.patch("google.auth.crypt.rsa._python_rsa", None) def test_from_string_delegates_to_cryptography(self, mock_crypto): expected_signer = mock.Mock() mock_crypto.RSASigner.from_string.return_value = expected_signer @@ -179,31 +130,6 @@ def test_from_string_delegates_to_cryptography(self, mock_crypto): PRIVATE_KEY_BYTES, key_id="123" ) - @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) - @mock.patch("google.auth.crypt.rsa._python_rsa") - def test_from_string_delegates_to_python_rsa(self, mock_python_rsa): - expected_signer = mock.Mock() - mock_python_rsa.RSASigner.from_string.return_value = expected_signer - - result = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES, key_id="123") - - assert result == expected_signer - mock_python_rsa.RSASigner.from_string.assert_called_once_with( - PRIVATE_KEY_BYTES, key_id="123" - ) - - @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) - @mock.patch("google.auth.crypt.rsa._python_rsa", None) - def test_from_string_missing_deps(self): - with pytest.raises(exceptions.MissingOptionalDependencyError) as e: - rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) - assert "RSASigner requires `cryptography`" in str(e) - assert ( - "`rsa` is also supported for legacy compatibility, but is deprecated" - in str(e) - ) - - @mock.patch("google.auth.crypt.rsa._python_rsa", None) def test_end_to_end_cryptography_lib(self): signer = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) message = b"Hello World" @@ -214,13 +140,12 @@ def test_end_to_end_cryptography_lib(self): assert isinstance(verifier, _cryptography_rsa.RSAVerifier) assert isinstance(signer, _cryptography_rsa.RSASigner) - @mock.patch("google.auth.crypt.rsa._cryptography_rsa", None) def test_end_to_end_rsa_lib(self): - signer = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) + signer = rsa.RSASigner(RSA_PRIVATE_KEY) message = b"Hello World" sig = signer.sign(message) - verifier = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + verifier = rsa.RSAVerifier(RSA_PUBLIC_KEY) result = verifier.verify(message, sig) assert bool(result) is True - assert isinstance(verifier, _python_rsa.RSAVerifier) - assert isinstance(signer, _python_rsa.RSASigner) + assert isinstance(verifier._impl, _python_rsa.RSAVerifier) + assert isinstance(signer._impl, _python_rsa.RSASigner) From 3dd119f03bf8949cb600327f9d59696c799a700e Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 15:54:16 -0800 Subject: [PATCH 33/50] Update google/auth/crypt/rsa.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- google/auth/crypt/rsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 6fbfdeb97..15370d50b 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -102,7 +102,7 @@ def __init__(self, private_key, key_id=None): elif module_str.startswith(CRYPTOGRAPHY_KEY_MODULE_PREFIX): impl_lib = _cryptography_rsa else: - raise ValueError(f"unrecognized private key type: {private_key}") + raise ValueError(f"unrecognized private key type: {type(private_key)}") self._impl = impl_lib.RSASigner(private_key, key_id=key_id) @property # type: ignore From 208299a3f223d65923aa449c33d26e4434f6ca1d Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 15:54:27 -0800 Subject: [PATCH 34/50] Update google/auth/crypt/rsa.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- google/auth/crypt/rsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 15370d50b..dedb034da 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -50,7 +50,7 @@ def __init__(self, public_key): elif module_str.startswith(CRYPTOGRAPHY_KEY_MODULE_PREFIX): impl_lib = _cryptography_rsa else: - raise ValueError(f"unrecognized public key type: {public_key}") + raise ValueError(f"unrecognized public key type: {type(public_key)}") self._impl = impl_lib.RSAVerifier(public_key) @_helpers.copy_docstring(base.Verifier) From 389807114fd89d07da2f825403a7608834364fa5 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 15:54:37 -0800 Subject: [PATCH 35/50] Update google/auth/crypt/rsa.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- google/auth/crypt/rsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index dedb034da..f3ed09240 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -116,7 +116,7 @@ def sign(self, message): @classmethod def from_string(cls, key, key_id=None): - """Construct an Signer instance from a private key in PEM format. + """Construct a Signer instance from a private key in PEM format. Args: key (str): Private key in PEM format. From 7fa0a84251ecd30fa3a72c38c172af3ba7d1b24b Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 16:07:03 -0800 Subject: [PATCH 36/50] improved init logic --- google/auth/crypt/rsa.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index f3ed09240..f89d5860a 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -19,13 +19,15 @@ for implmentations using different third party libraries """ +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey from google.auth import _helpers from google.auth.crypt import base from google.auth.crypt import _cryptography_rsa from google.auth.crypt import _python_rsa RSA_KEY_MODULE_PREFIX = "rsa.key" -CRYPTOGRAPHY_KEY_MODULE_PREFIX = "cryptography." + class RSAVerifier(base.Verifier): """Verifies RSA cryptographic signatures using public keys. @@ -45,10 +47,10 @@ class RSAVerifier(base.Verifier): def __init__(self, public_key): module_str = public_key.__class__.__module__ - if module_str.startswith(RSA_KEY_MODULE_PREFIX): - impl_lib = _python_rsa - elif module_str.startswith(CRYPTOGRAPHY_KEY_MODULE_PREFIX): + if isinstance(public_key, RSAPublicKey): impl_lib = _cryptography_rsa + elif module_str.startswith(RSA_KEY_MODULE_PREFIX): + impl_lib = _python_rsa else: raise ValueError(f"unrecognized public key type: {type(public_key)}") self._impl = impl_lib.RSAVerifier(public_key) @@ -97,10 +99,10 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): def __init__(self, private_key, key_id=None): module_str = private_key.__class__.__module__ - if module_str.startswith(RSA_KEY_MODULE_PREFIX): - impl_lib = _python_rsa - elif module_str.startswith(CRYPTOGRAPHY_KEY_MODULE_PREFIX): + if isinstance(private_key, RSAPrivateKey): impl_lib = _cryptography_rsa + elif module_str.startswith(RSA_KEY_MODULE_PREFIX): + impl_lib = _python_rsa else: raise ValueError(f"unrecognized private key type: {type(private_key)}") self._impl = impl_lib.RSASigner(private_key, key_id=key_id) From 2958fe14dfad4fb7c99461e415a7bb1ae08bf7b8 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 16:07:17 -0800 Subject: [PATCH 37/50] fixed lint --- google/auth/crypt/rsa.py | 3 ++- google/auth/exceptions.py | 2 +- tests/crypt/test__python_rsa.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index f89d5860a..1682b6a27 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -21,10 +21,11 @@ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey + from google.auth import _helpers -from google.auth.crypt import base from google.auth.crypt import _cryptography_rsa from google.auth.crypt import _python_rsa +from google.auth.crypt import base RSA_KEY_MODULE_PREFIX = "rsa.key" diff --git a/google/auth/exceptions.py b/google/auth/exceptions.py index c9b1ee76b..feb9f7411 100644 --- a/google/auth/exceptions.py +++ b/google/auth/exceptions.py @@ -105,4 +105,4 @@ class TimeoutError(GoogleAuthError): class ResponseError(GoogleAuthError): - """Used to indicate an error occurred when reading an HTTP response.""" \ No newline at end of file + """Used to indicate an error occurred when reading an HTTP response.""" diff --git a/tests/crypt/test__python_rsa.py b/tests/crypt/test__python_rsa.py index a63b665d1..43539900f 100644 --- a/tests/crypt/test__python_rsa.py +++ b/tests/crypt/test__python_rsa.py @@ -200,4 +200,4 @@ def test_import_warning(self): with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): _python_rsa.RSAVerifier(None) with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): - _python_rsa.RSASigner(None) \ No newline at end of file + _python_rsa.RSASigner(None) From 1ac38ff763032fa2fdd9d79d1b413b4762303ef9 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 16:14:02 -0800 Subject: [PATCH 38/50] improved from_string --- google/auth/crypt/rsa.py | 8 ++++++-- tests/crypt/test_rsa.py | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 1682b6a27..fa4aa7e86 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -75,7 +75,9 @@ def from_string(cls, public_key): Raises: ValueError: If the public_key can't be parsed. """ - return _cryptography_rsa.RSAVerifier.from_string(public_key) + instance = cls.__new__(cls) + instance._impl = _cryptography_rsa.RSAVerifier.from_string(public_key) + return instance class RSASigner(base.Signer, base.FromServiceAccountMixin): @@ -132,4 +134,6 @@ def from_string(cls, key, key_id=None): ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in PEM format. """ - return _cryptography_rsa.RSASigner.from_string(key, key_id=key_id) + instance = cls.__new__(cls) + instance._impl = _cryptography_rsa.RSASigner.from_string(key, key_id=key_id) + return instance diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index 100c02fbc..e1fa79b36 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -81,7 +81,7 @@ def test_from_string_cryptography(self, mock_crypto): result = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) - assert result == expected_verifier + assert result._impl == expected_verifier mock_crypto.RSAVerifier.from_string.assert_called_once_with(PUBLIC_KEY_BYTES) @@ -125,7 +125,7 @@ def test_from_string_delegates_to_cryptography(self, mock_crypto): result = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES, key_id="123") - assert result == expected_signer + assert result._impl == expected_signer mock_crypto.RSASigner.from_string.assert_called_once_with( PRIVATE_KEY_BYTES, key_id="123" ) @@ -137,8 +137,8 @@ def test_end_to_end_cryptography_lib(self): verifier = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) result = verifier.verify(message, sig) assert result is True - assert isinstance(verifier, _cryptography_rsa.RSAVerifier) - assert isinstance(signer, _cryptography_rsa.RSASigner) + assert isinstance(verifier._impl, _cryptography_rsa.RSAVerifier) + assert isinstance(signer._impl, _cryptography_rsa.RSASigner) def test_end_to_end_rsa_lib(self): signer = rsa.RSASigner(RSA_PRIVATE_KEY) From 9a8043fb188a386783cc1dc248b2c7129abb5c9d Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 16:18:21 -0800 Subject: [PATCH 39/50] import rsa only when needed --- google/auth/crypt/rsa.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index fa4aa7e86..0eaecbcb4 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -24,7 +24,6 @@ from google.auth import _helpers from google.auth.crypt import _cryptography_rsa -from google.auth.crypt import _python_rsa from google.auth.crypt import base RSA_KEY_MODULE_PREFIX = "rsa.key" @@ -43,6 +42,7 @@ class RSAVerifier(base.Verifier): public_key (Union[rsa.key.PublicKey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey]): The public key used to verify signatures. Raises: + ImportError: if called with an rsa.key.PublicKey, when the rsa library is not installed ValueError: if an unrecognized public key is provided """ @@ -51,6 +51,7 @@ def __init__(self, public_key): if isinstance(public_key, RSAPublicKey): impl_lib = _cryptography_rsa elif module_str.startswith(RSA_KEY_MODULE_PREFIX): + from google.auth.crypt import _python_rsa impl_lib = _python_rsa else: raise ValueError(f"unrecognized public key type: {type(public_key)}") @@ -97,6 +98,7 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): public key or certificate. Raises: + ImportError: if called with an rsa.key.PublicKey, when the rsa library is not installed ValueError: if an unrecognized public key is provided """ @@ -105,6 +107,7 @@ def __init__(self, private_key, key_id=None): if isinstance(private_key, RSAPrivateKey): impl_lib = _cryptography_rsa elif module_str.startswith(RSA_KEY_MODULE_PREFIX): + from google.auth.crypt import _python_rsa impl_lib = _python_rsa else: raise ValueError(f"unrecognized private key type: {type(private_key)}") From 4edfd72efcfd624f15027ce1d8bb0cdcd416094c Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 16:19:59 -0800 Subject: [PATCH 40/50] updated dependencies --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index a8dd78a2d..966d14415 100644 --- a/setup.py +++ b/setup.py @@ -22,8 +22,6 @@ DEPENDENCIES = ( "pyasn1-modules>=0.2.1", "cryptography >= 38.0.3", - # TODO: remove rsa from dependencies in next release (replaced with cryptography) - "rsa>=3.1.4,<5", ) # Note: cryptography was made into a required dependency. Extra is kept for backwards compatibility @@ -74,6 +72,9 @@ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1722): `test_aiohttp_requests` depend on # aiohttp < 3.10.0 which is a bug. Investigate and remove the pinned aiohttp version. "aiohttp < 3.10.0", + # rsa library was removed as a dependency, but we still have some code paths that support it + # TODO: remove dependency when google.auth.crypt._python_rsa is removed + "rsa>=3.1.4,<5", ] extras = { From a11cb0f117a211f392aea8482a4473b42f42af4e Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 12 Jan 2026 16:54:49 -0800 Subject: [PATCH 41/50] fixed test --- tests/test_jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_jwt.py b/tests/test_jwt.py index 9502bc32e..4c5988469 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -129,7 +129,7 @@ def factory( # False is specified to remove the signer's key id for testing # headers without key ids. if key_id is False: - signer._key_id = None + signer._impl._key_id = None key_id = None if use_es256_signer: From fb5279664e9ae1a88f8a7363ca762b4449d1b10c Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 13 Jan 2026 10:20:13 -0800 Subject: [PATCH 42/50] removed import check in __init__ --- google/auth/crypt/__init__.py | 46 +++++++++++++++-------------------- google/auth/crypt/es.py | 4 +-- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/google/auth/crypt/__init__.py b/google/auth/crypt/__init__.py index 59519b475..e36872d2c 100644 --- a/google/auth/crypt/__init__.py +++ b/google/auth/crypt/__init__.py @@ -40,33 +40,13 @@ from google.auth.crypt import base from google.auth.crypt import rsa -# google.auth.crypt.es depends on the crytpography module which may not be -# successfully imported depending on the system. -try: - from google.auth.crypt import es - from google.auth.crypt import es256 -except ImportError: # pragma: NO COVER - es = None # type: ignore - es256 = None # type: ignore - -if es is not None and es256 is not None: # pragma: NO COVER - __all__ = [ - "EsSigner", - "EsVerifier", - "ES256Signer", - "ES256Verifier", - "RSASigner", - "RSAVerifier", - "Signer", - "Verifier", - ] - - EsSigner = es.EsSigner - EsVerifier = es.EsVerifier - ES256Signer = es256.ES256Signer - ES256Verifier = es256.ES256Verifier -else: # pragma: NO COVER - __all__ = ["RSASigner", "RSAVerifier", "Signer", "Verifier"] +from google.auth.crypt import es +from google.auth.crypt import es256 + +EsSigner = es.EsSigner +EsVerifier = es.EsVerifier +ES256Signer = es256.ES256Signer +ES256Verifier = es256.ES256Verifier # Aliases to maintain the v1.0.0 interface, as the crypt module was split @@ -103,3 +83,15 @@ class to use for verification. This can be used to select different if verifier.verify(message, signature): return True return False + + +__all__ = [ + "EsSigner", + "EsVerifier", + "ES256Signer", + "ES256Verifier", + "RSASigner", + "RSAVerifier", + "Signer", + "Verifier", +] diff --git a/google/auth/crypt/es.py b/google/auth/crypt/es.py index f9466af3c..dbbe56b3f 100644 --- a/google/auth/crypt/es.py +++ b/google/auth/crypt/es.py @@ -102,7 +102,7 @@ def verify(self, message: bytes, signature: bytes) -> bool: @classmethod def from_string(cls, public_key: Union[str, bytes]) -> "EsVerifier": - """Construct an Verifier instance from a public key or public + """Construct a Verifier instance from a public key or public certificate string. Args: @@ -110,7 +110,7 @@ def from_string(cls, public_key: Union[str, bytes]) -> "EsVerifier": x509 public key certificate. Returns: - Verifier: The constructed verifier. + google.auth.crypt.Verifier: The constructed verifier. Raises: ValueError: If the public key can't be parsed. From b455d59400983af636e7468c581429fef745e966 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 13 Jan 2026 11:37:51 -0800 Subject: [PATCH 43/50] fixed lint --- google/auth/crypt/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/google/auth/crypt/__init__.py b/google/auth/crypt/__init__.py index e36872d2c..e56bc7b82 100644 --- a/google/auth/crypt/__init__.py +++ b/google/auth/crypt/__init__.py @@ -38,10 +38,9 @@ """ from google.auth.crypt import base -from google.auth.crypt import rsa - from google.auth.crypt import es from google.auth.crypt import es256 +from google.auth.crypt import rsa EsSigner = es.EsSigner EsVerifier = es.EsVerifier From 2416c6f17df3e841af8d605592e88626d29d5d41 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Wed, 14 Jan 2026 16:09:00 -0800 Subject: [PATCH 44/50] addressed PR comments --- google/auth/crypt/rsa.py | 16 +------ setup.py | 16 +++---- tests/crypt/test_rsa.py | 100 +++++++++++++++++++++++---------------- 3 files changed, 68 insertions(+), 64 deletions(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index fa4aa7e86..4b2fb39ff 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -33,14 +33,8 @@ class RSAVerifier(base.Verifier): """Verifies RSA cryptographic signatures using public keys. - Requires installation of `cryptography` optional dependency. - - .. deprecated:: - The `rsa` library has been archived. Please migrate to - `cryptography` for public keys. - Args: - public_key (Union[rsa.key.PublicKey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey]): + public_key (Union["rsa.key.PublicKey", cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey]): The public key used to verify signatures. Raises: ValueError: if an unrecognized public key is provided @@ -83,14 +77,8 @@ def from_string(cls, public_key): class RSASigner(base.Signer, base.FromServiceAccountMixin): """Signs messages with an RSA private key. - Requires installation of `cryptography` optional dependency. - - .. deprecated:: - The `rsa` library has been archived. Please migrate to - `cryptography` for public keys. - Args: - private_key (Union[rsa.key.PrivateKey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey]): + private_key (Union["rsa.key.PrivateKey", cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey]): The private key to sign with. key_id (str): Optional key ID used to identify this private key. This can be useful to associate the private key with its associated diff --git a/setup.py b/setup.py index a8dd78a2d..e28cd54d8 100644 --- a/setup.py +++ b/setup.py @@ -18,19 +18,18 @@ from setuptools import find_namespace_packages from setuptools import setup +cryptography_base_require = [ + "cryptography >= 38.0.3", +] DEPENDENCIES = ( "pyasn1-modules>=0.2.1", - "cryptography >= 38.0.3", - # TODO: remove rsa from dependencies in next release (replaced with cryptography) + cryptography_base_require, + # TODO: remove rsa from dependencies in next release (replaced with cryptography)i + # https://github.com/googleapis/google-auth-library-python/issues/1810 "rsa>=3.1.4,<5", ) -# Note: cryptography was made into a required dependency. Extra is kept for backwards compatibility -cryptography_extra_require = [ - "cryptography >= 38.0.3", -] - requests_extra_require = ["requests >= 2.20.0, < 3.0.0"] aiohttp_extra_require = ["aiohttp >= 3.6.2, < 4.0.0", *requests_extra_require] @@ -77,7 +76,8 @@ ] extras = { - "cryptography": cryptography_extra_require, + # Note: cryptography was made into a required dependency. Extra is kept for backwards compatibility + "cryptography": cryptography_base_require, "aiohttp": aiohttp_extra_require, "enterprise_cert": enterprise_cert_extra_require, "pyopenssl": pyopenssl_extra_require, diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index e1fa79b36..5442efb15 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -27,35 +27,51 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") -with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: - PRIVATE_KEY_BYTES = fh.read() - CRYPTOGRAPHY_PRIVATE_KEY = serialization.load_pem_private_key( - PRIVATE_KEY_BYTES, password=None, backend=backends.default_backend() +@pytest.fixture +def private_key_bytes(): + with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: + return fh.read() + +@pytest.fixture +def public_key_bytes(): + with open(os.path.join(DATA_DIR, "privatekey.pub"), "rb") as fh: + return fh.read() + +@pytest.fixture +def cryptography_private_key(private_key_bytes): + return serialization.load_pem_private_key( + private_key_bytes, password=None, backend=backends.default_backend() ) - RSA_PRIVATE_KEY = rsa_lib.PrivateKey.load_pkcs1(PRIVATE_KEY_BYTES) -with open(os.path.join(DATA_DIR, "privatekey.pub"), "rb") as fh: - PUBLIC_KEY_BYTES = fh.read() - CRYPTOGRAPHY_PUBLIC_KEY = serialization.load_pem_public_key( - PUBLIC_KEY_BYTES, backend=backends.default_backend() +@pytest.fixture +def rsa_private_key(private_key_bytes): + return rsa_lib.PrivateKey.load_pkcs1(private_key_bytes) + +@pytest.fixture +def cryptography_public_key(public_key_bytes): + return serialization.load_pem_public_key( + public_key_bytes, backend=backends.default_backend() ) - RSA_PUBLIC_KEY = rsa_lib.PublicKey.load_pkcs1(PUBLIC_KEY_BYTES) + +@pytest.fixture +def rsa_public_key(public_key_bytes): + return rsa_lib.PublicKey.load_pkcs1(public_key_bytes) class TestRSAVerifier: - def test_init_with_cryptography_key(self): - verifier = rsa.RSAVerifier(CRYPTOGRAPHY_PUBLIC_KEY) + def test_init_with_cryptography_key(self, cryptography_public_key): + verifier = rsa.RSAVerifier(cryptography_public_key) assert isinstance(verifier._impl, _cryptography_rsa.RSAVerifier) - assert verifier._impl._pubkey == CRYPTOGRAPHY_PUBLIC_KEY + assert verifier._impl._pubkey == cryptography_public_key - def test_init_with_rsa_key(self): - verifier = rsa.RSAVerifier(RSA_PUBLIC_KEY) + def test_init_with_rsa_key(self, rsa_public_key): + verifier = rsa.RSAVerifier(rsa_public_key) assert isinstance(verifier._impl, _python_rsa.RSAVerifier) - assert verifier._impl._pubkey == RSA_PUBLIC_KEY + assert verifier._impl._pubkey == rsa_public_key - def test_warning_with_rsa(self): + def test_warning_with_rsa(self, rsa_public_key): with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): - rsa.RSAVerifier(RSA_PUBLIC_KEY) + rsa.RSAVerifier(rsa_public_key) def test_init_with_unknown_key(self): unknown_key = object() @@ -63,8 +79,8 @@ def test_init_with_unknown_key(self): with pytest.raises(ValueError): rsa.RSAVerifier(unknown_key) - def test_verify_delegates(self): - verifier = rsa.RSAVerifier(CRYPTOGRAPHY_PUBLIC_KEY) + def test_verify_delegates(self, cryptography_public_key): + verifier = rsa.RSAVerifier(cryptography_public_key) # Mock the implementation's verify method with mock.patch.object( @@ -75,32 +91,32 @@ def test_verify_delegates(self): mock_verify.assert_called_once_with(b"message", b"signature") @mock.patch("google.auth.crypt.rsa._cryptography_rsa") - def test_from_string_cryptography(self, mock_crypto): + def test_from_string_cryptography(self, mock_crypto, public_key_bytes): expected_verifier = mock.Mock() mock_crypto.RSAVerifier.from_string.return_value = expected_verifier - result = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + result = rsa.RSAVerifier.from_string(public_key_bytes) assert result._impl == expected_verifier - mock_crypto.RSAVerifier.from_string.assert_called_once_with(PUBLIC_KEY_BYTES) + mock_crypto.RSAVerifier.from_string.assert_called_once_with(public_key_bytes) class TestRSASigner: - def test_init_with_cryptography_key(self): - signer = rsa.RSASigner(CRYPTOGRAPHY_PRIVATE_KEY, key_id="123") + def test_init_with_cryptography_key(self, cryptography_private_key): + signer = rsa.RSASigner(cryptography_private_key, key_id="123") assert isinstance(signer._impl, _cryptography_rsa.RSASigner) - assert signer._impl._key == CRYPTOGRAPHY_PRIVATE_KEY + assert signer._impl._key == cryptography_private_key assert signer._impl.key_id == "123" - def test_init_with_rsa_key(self): - signer = rsa.RSASigner(RSA_PRIVATE_KEY, key_id="123") + def test_init_with_rsa_key(self, rsa_private_key): + signer = rsa.RSASigner(rsa_private_key, key_id="123") assert isinstance(signer._impl, _python_rsa.RSASigner) - assert signer._impl._key == RSA_PRIVATE_KEY + assert signer._impl._key == rsa_private_key assert signer._impl.key_id == "123" - def test_warning_with_rsa(self): + def test_warning_with_rsa(self, rsa_private_key): with pytest.warns(DeprecationWarning, match="The 'rsa' library is deprecated"): - rsa.RSASigner(RSA_PRIVATE_KEY, key_id="123") + rsa.RSASigner(rsa_private_key, key_id="123") def test_init_with_unknown_key(self): unknown_key = object() @@ -108,8 +124,8 @@ def test_init_with_unknown_key(self): with pytest.raises(ValueError): rsa.RSASigner(unknown_key) - def test_sign_delegates(self): - signer = rsa.RSASigner(RSA_PRIVATE_KEY) + def test_sign_delegates(self, rsa_private_key): + signer = rsa.RSASigner(rsa_private_key) with mock.patch.object( signer._impl, "sign", return_value=b"signature" @@ -119,32 +135,32 @@ def test_sign_delegates(self): mock_sign.assert_called_once_with(b"message") @mock.patch("google.auth.crypt.rsa._cryptography_rsa") - def test_from_string_delegates_to_cryptography(self, mock_crypto): + def test_from_string_delegates_to_cryptography(self, mock_crypto, private_key_bytes): expected_signer = mock.Mock() mock_crypto.RSASigner.from_string.return_value = expected_signer - result = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES, key_id="123") + result = rsa.RSASigner.from_string(private_key_bytes, key_id="123") assert result._impl == expected_signer mock_crypto.RSASigner.from_string.assert_called_once_with( - PRIVATE_KEY_BYTES, key_id="123" + private_key_bytes, key_id="123" ) - def test_end_to_end_cryptography_lib(self): - signer = rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) + def test_end_to_end_cryptography_lib(self, private_key_bytes, public_key_bytes): + signer = rsa.RSASigner.from_string(private_key_bytes) message = b"Hello World" sig = signer.sign(message) - verifier = rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + verifier = rsa.RSAVerifier.from_string(public_key_bytes) result = verifier.verify(message, sig) assert result is True assert isinstance(verifier._impl, _cryptography_rsa.RSAVerifier) assert isinstance(signer._impl, _cryptography_rsa.RSASigner) - def test_end_to_end_rsa_lib(self): - signer = rsa.RSASigner(RSA_PRIVATE_KEY) + def test_end_to_end_rsa_lib(self, rsa_private_key, rsa_public_key): + signer = rsa.RSASigner(rsa_private_key) message = b"Hello World" sig = signer.sign(message) - verifier = rsa.RSAVerifier(RSA_PUBLIC_KEY) + verifier = rsa.RSAVerifier(rsa_public_key) result = verifier.verify(message, sig) assert bool(result) is True assert isinstance(verifier._impl, _python_rsa.RSAVerifier) From 1edd592a6b9c948481200a2bf6743d14076dfebe Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Wed, 14 Jan 2026 17:56:09 -0800 Subject: [PATCH 45/50] fixed lint --- tests/crypt/test_rsa.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index 5442efb15..6f7aa2691 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -27,32 +27,38 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") + @pytest.fixture def private_key_bytes(): with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: return fh.read() + @pytest.fixture def public_key_bytes(): with open(os.path.join(DATA_DIR, "privatekey.pub"), "rb") as fh: return fh.read() + @pytest.fixture def cryptography_private_key(private_key_bytes): return serialization.load_pem_private_key( private_key_bytes, password=None, backend=backends.default_backend() ) + @pytest.fixture def rsa_private_key(private_key_bytes): return rsa_lib.PrivateKey.load_pkcs1(private_key_bytes) + @pytest.fixture def cryptography_public_key(public_key_bytes): return serialization.load_pem_public_key( public_key_bytes, backend=backends.default_backend() ) + @pytest.fixture def rsa_public_key(public_key_bytes): return rsa_lib.PublicKey.load_pkcs1(public_key_bytes) @@ -135,7 +141,9 @@ def test_sign_delegates(self, rsa_private_key): mock_sign.assert_called_once_with(b"message") @mock.patch("google.auth.crypt.rsa._cryptography_rsa") - def test_from_string_delegates_to_cryptography(self, mock_crypto, private_key_bytes): + def test_from_string_delegates_to_cryptography( + self, mock_crypto, private_key_bytes + ): expected_signer = mock.Mock() mock_crypto.RSASigner.from_string.return_value = expected_signer From ba8b25e3e094830b686d068caf3c406f075c0540 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 15 Jan 2026 15:09:34 -0800 Subject: [PATCH 46/50] updated cryptography requirement in enterprise_cert_extra --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index e28cd54d8..ba9e214b1 100644 --- a/setup.py +++ b/setup.py @@ -38,8 +38,8 @@ reauth_extra_require = ["pyu2f>=0.1.5"] -# TODO(https://github.com/googleapis/google-auth-library-python/issues/1738): Add bounds for cryptography and pyopenssl dependencies. -enterprise_cert_extra_require = ["cryptography", "pyopenssl"] +# TODO(https://github.com/googleapis/google-auth-library-python/issues/1738): Add bounds for pyopenssl dependency. +enterprise_cert_extra_require = ["pyopenssl"] pyopenssl_extra_require = ["pyopenssl>=20.0.0"] From 6efed1db6f11d173787083ee48fbf94d7aebbfb7 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 15 Jan 2026 19:45:04 -0800 Subject: [PATCH 47/50] Update google/auth/crypt/rsa.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- google/auth/crypt/rsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 0eaecbcb4..58e42168e 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -98,7 +98,7 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): public key or certificate. Raises: - ImportError: if called with an rsa.key.PublicKey, when the rsa library is not installed + ImportError: if called with an rsa.key.PrivateKey, when the rsa library is not installed ValueError: if an unrecognized public key is provided """ From d3ab2e71002625fed765546f678e6c802418caa4 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 15 Jan 2026 19:46:52 -0800 Subject: [PATCH 48/50] fixed lint --- google/auth/crypt/rsa.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index d6b3cb29f..639be9069 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -46,6 +46,7 @@ def __init__(self, public_key): impl_lib = _cryptography_rsa elif module_str.startswith(RSA_KEY_MODULE_PREFIX): from google.auth.crypt import _python_rsa + impl_lib = _python_rsa else: raise ValueError(f"unrecognized public key type: {type(public_key)}") @@ -96,6 +97,7 @@ def __init__(self, private_key, key_id=None): impl_lib = _cryptography_rsa elif module_str.startswith(RSA_KEY_MODULE_PREFIX): from google.auth.crypt import _python_rsa + impl_lib = _python_rsa else: raise ValueError(f"unrecognized private key type: {type(private_key)}") From 971cbd76e5bc9bd8cdd7deb9d639f6615d6aa6eb Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 16 Jan 2026 10:07:36 -0800 Subject: [PATCH 49/50] fixed mypy --- tests/crypt/test_rsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/crypt/test_rsa.py b/tests/crypt/test_rsa.py index 6f7aa2691..6ed822ad9 100644 --- a/tests/crypt/test_rsa.py +++ b/tests/crypt/test_rsa.py @@ -18,7 +18,7 @@ from cryptography.hazmat import backends from cryptography.hazmat.primitives import serialization import pytest -import rsa as rsa_lib +import rsa as rsa_lib # type: ignore from google.auth.crypt import _cryptography_rsa from google.auth.crypt import _python_rsa From b43f691e17de80c6987287faa5adaf7dcefa29a8 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 16 Jan 2026 10:24:19 -0800 Subject: [PATCH 50/50] remove rsa from constraints file --- testing/constraints-3.7.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/constraints-3.7.txt b/testing/constraints-3.7.txt index 52ad3af91..d9655a360 100644 --- a/testing/constraints-3.7.txt +++ b/testing/constraints-3.7.txt @@ -7,7 +7,6 @@ # Then this file should have foo==1.14.0 pyasn1-modules==0.2.1 setuptools==40.3.0 -rsa==3.1.4 aiohttp==3.6.2 requests==2.20.0 pyjwt==2.0 \ No newline at end of file