Skip to content

[DRAFT] fix: remove dependency on rsa#1928

Closed
daniel-sanche wants to merge 31 commits intomainfrom
remove_rsa_2
Closed

[DRAFT] fix: remove dependency on rsa#1928
daniel-sanche wants to merge 31 commits intomainfrom
remove_rsa_2

Conversation

@daniel-sanche
Copy link
Collaborator

The rsa library has been deprecated and archived. This PR will remove it as a required dependency. Users are advised to use the optional cryptography dependency (google-auth[cryptography]) instead.

In the short term, rsa will still be supported as an optional dependency. In the long term, it will eventually be removed

As a part of this change, I introduced new RSASigner and RSAVerifier wrapper classes, that can use either cryptography or rsa implementations. Previously, the library would only use one, depending on if cryptography was installed. This simplifies the import structure, and puts rsa and cryptography on equal footing as optional dependencies

Note: Do not merge yet. We will do one release giving a warning, and then this change will come in a future release. We plan on doing a pre-release on pypi of this branch, so users can opt-in to the rsa-free build early

@daniel-sanche daniel-sanche added the do not merge Indicates a pull request not ready for merge, due to either quality or timing. label Jan 10, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @daniel-sanche, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the RSA cryptographic signing and verification mechanisms within the library. The primary goal is to remove the mandatory dependency on the deprecated rsa library, offering a more robust and future-proof solution by prioritizing the cryptography library while maintaining backward compatibility for rsa as an optional dependency. This change enhances flexibility and provides a clear migration path for users.

Highlights

  • Dependency Management: The rsa library has been transitioned from a required dependency to an optional one, due to its deprecation and archiving. Users are now encouraged to use google-auth[cryptography].
  • RSA Implementation Abstraction: New RSASigner and RSAVerifier wrapper classes have been introduced in google.auth.crypt.rsa. These wrappers dynamically select between the cryptography library (preferred) and the rsa library for cryptographic operations, based on the type of key provided.
  • Deprecation Warnings: Deprecation warnings are now issued when directly instantiating RSASigner or RSAVerifier from google.auth.crypt._python_rsa, guiding users towards the cryptography alternative.
  • Improved Error Handling: A new MissingOptionalDependencyError exception has been added to provide clearer messages when required optional dependencies for RSA operations are not installed.
  • Comprehensive Testing: A new test file (tests/crypt/test_rsa.py) has been added to thoroughly validate the behavior of the new wrapper classes, including dependency handling, key type recognition, and delegation of cryptographic operations.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request removes the hard dependency on the rsa library by making it an optional dependency and introducing wrapper classes for RSASigner and RSAVerifier. These wrappers can delegate to either cryptography or rsa implementations. This is a good step towards completely removing the deprecated rsa library.

My review has found a critical issue in the implementation of the new wrapper classes in google/auth/crypt/rsa.py. The from_string factory methods incorrectly return instances of the underlying implementation classes instead of the wrapper classes themselves, which breaks the abstraction. I've provided suggestions to fix this. Additionally, I've pointed out some related issues in the new tests and a few minor documentation typos.

Comment on lines +76 to +96
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 MissingOptionalDependencyError.create(cls, "cryptography", RSA_NOTE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The from_string classmethod returns an instance of the underlying implementation class (e.g., _cryptography_rsa.RSAVerifier) instead of the wrapper class rsa.RSAVerifier. This breaks the abstraction, as callers would expect an instance of rsa.RSAVerifier.

The method should create an instance of the wrapper class and set its internal _impl to the instance created by the underlying library.

Additionally, there are a few typos in the docstring (an Verifier should be a Verifier, cryptograhy should be cryptography, and installe should be installed).

    def from_string(cls, public_key):
        """Construct a 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 `cryptography` or `rsa` is installed
        """
        if _cryptography_rsa:
            impl = _cryptography_rsa.RSAVerifier.from_string(public_key)
            verifier = cls.__new__(cls)
            verifier._impl = impl
            return verifier
        elif _python_rsa:
            impl = _python_rsa.RSAVerifier.from_string(public_key)
            verifier = cls.__new__(cls)
            verifier._impl = impl
            return verifier
        else:
            raise MissingOptionalDependencyError.create(cls, "cryptography", RSA_NOTE)

Comment on lines +143 to +163
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.

RSASigner = _python_rsa.RSASigner # type: ignore
RSAVerifier = _python_rsa.RSAVerifier # type: ignore
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.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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Similar to RSAVerifier.from_string, this method returns an instance of the underlying implementation class instead of the rsa.RSASigner wrapper class. This should be corrected to return an instance of the wrapper.

Also, there are typos in the docstring (cryptograhy, installe).

    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 `cryptography` or `rsa` is installed
        """
        if _cryptography_rsa:
            impl = _cryptography_rsa.RSASigner.from_string(key, key_id=key_id)
            signer = cls.__new__(cls)
            signer._impl = impl
            return signer
        elif _python_rsa:
            impl = _python_rsa.RSASigner.from_string(key, key_id=key_id)
            signer = cls.__new__(cls)
            signer._impl = impl
            return signer
        else:
            raise MissingOptionalDependencyError.create(cls, "cryptography", RSA_NOTE)

Comment on lines +53 to +55
Raises:
ImportError: if neither `cryptograhy` or `rsa` is installed
ValueError: if an unrecognized public key is provided
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a typo in the Raises section of the docstring. cryptograhy should be cryptography.

Suggested change
Raises:
ImportError: if neither `cryptograhy` or `rsa` is installed
ValueError: if an unrecognized public key is provided
Raises:
ImportError: if neither `cryptography` or `rsa` is installed
ValueError: if an unrecognized public key is provided

Comment on lines +115 to +117
Raises:
ImportError: if neither `cryptograhy` or `rsa` is installed
ValueError: if an unrecognized public key is provided
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a typo in the Raises section of the docstring. cryptograhy should be cryptography.

    Raises:
        ImportError: if neither `cryptography` or `rsa` is installed
        ValueError: if an unrecognized public key is provided

Comment on lines +214 to +215
assert isinstance(verifier, _cryptography_rsa.RSAVerifier)
assert isinstance(signer, _cryptography_rsa.RSASigner)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The isinstance checks here are incorrect because rsa.RSAVerifier.from_string and rsa.RSASigner.from_string are returning instances of the implementation class, not the wrapper class.

Once the from_string methods are fixed to return wrapper instances, this test should be updated to check for the wrapper type, and then optionally check the type of the _impl attribute. For example:

assert isinstance(verifier, rsa.RSAVerifier)
assert isinstance(verifier._impl, _cryptography_rsa.RSAVerifier)
assert isinstance(signer, rsa.RSASigner)
assert isinstance(signer._impl, _cryptography_rsa.RSASigner)

Comment on lines +224 to +226
assert bool(result) is True
assert isinstance(verifier, _python_rsa.RSAVerifier)
assert isinstance(signer, _python_rsa.RSASigner)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test has a few issues:

  1. The isinstance checks are incorrect and should be updated to check for the wrapper type, similar to test_end_to_end_cryptography_lib.
  2. assert bool(result) is True is inconsistent with the other test and can be simplified to assert result is True since the verify method returns a boolean.

After fixing the from_string methods in google/auth/crypt/rsa.py, this test should be updated as follows:

...
result = verifier.verify(message, sig)
assert result is True
assert isinstance(verifier, rsa.RSAVerifier)
assert isinstance(verifier._impl, _python_rsa.RSAVerifier)
assert isinstance(signer, rsa.RSASigner)
assert isinstance(signer._impl, _python_rsa.RSASigner)

@daniel-sanche
Copy link
Collaborator Author

After some discussion, we will make cryptography a required dependency, instead of leaving them both as optional. Closing this in favor of https://github.com/googleapis/google-auth-library-python/compare/add_cryptography_dependency

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do not merge Indicates a pull request not ready for merge, due to either quality or timing.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant