From c9a5b211725e1aa5699cabee4057bf5da9887d71 Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Tue, 31 Mar 2026 06:45:06 +0000 Subject: [PATCH 01/10] maintenance ssl --- gslib/commands/signurl.py | 52 ++++++++---------------- gslib/tests/test_gcs_json_credentials.py | 10 ++--- setup.py | 3 +- 3 files changed, 24 insertions(+), 41 deletions(-) diff --git a/gslib/commands/signurl.py b/gslib/commands/signurl.py index 2df8b5e3be..682d4f6993 100644 --- a/gslib/commands/signurl.py +++ b/gslib/commands/signurl.py @@ -51,22 +51,9 @@ from gslib.utils.shim_util import GcloudStorageMap, GcloudStorageFlag from gslib.utils.signurl_helper import CreatePayload, GetFinalUrl, to_bytes -try: - # Check for openssl. - # pylint: disable=C6204 - from OpenSSL.crypto import FILETYPE_PEM - from OpenSSL.crypto import load_privatekey - from OpenSSL.crypto import sign - from OpenSSL.crypto import PKey - HAVE_OPENSSL = True -except ImportError: - load_privatekey = None - sign = None - HAVE_OPENSSL = False - FILETYPE_PEM = None - try: from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey from cryptography.hazmat.primitives.serialization import pkcs12 @@ -76,6 +63,7 @@ HAVE_CRYPTO = False _CRYPTO_IMPORT_ERROR = "pyca/cryptography is not available. Either install it, or please consider using the .json keyfile" +HAVE_OPENSSL = HAVE_CRYPTO _AUTO_DETECT_REGION = 'auto' _MAX_EXPIRATION_TIME = timedelta(days=7) _MAX_EXPIRATION_TIME_WITH_MINUS_U = timedelta(hours=12) @@ -307,12 +295,6 @@ def _GenSignedUrl(key, signed_headers=signed_headers, string_to_sign_debug=string_to_sign_debug) else: - if six.PY2: - digest = b'RSA-SHA256' - else: - # Your IDE may complain about this due to a bad docstring in pyOpenSsl: - # https://github.com/pyca/pyopenssl/issues/741 - digest = 'RSA-SHA256' string_to_sign, canonical_query_string = CreatePayload( client_id=client_id, method=method, @@ -324,12 +306,10 @@ def _GenSignedUrl(key, signed_headers=signed_headers, billing_project=billing_project, string_to_sign_debug=string_to_sign_debug) - if isinstance(key, PKey): - raw_signature = sign(key, string_to_sign, digest) - else: - if not HAVE_CRYPTO: - raise CommandException(_CRYPTO_IMPORT_ERROR) - raw_signature = key.sign(to_bytes(string_to_sign), padding.PKCS1v15(), hashes.SHA256()) + + if not HAVE_CRYPTO: + raise CommandException(_CRYPTO_IMPORT_ERROR) + raw_signature = key.sign(to_bytes(string_to_sign), padding.PKCS1v15(), hashes.SHA256()) final_url = GetFinalUrl(raw_signature, gs_host, gcs_path, canonical_query_string) return final_url @@ -353,8 +333,7 @@ def _ReadJSONKeystore(ks_contents, passwd=None): Assumes this keystore was downloaded from the Cloud Platform Console. By default, JSON keystore private keys from the Cloud Platform Console - aren't encrypted so the passwd is optional as load_privatekey will - prompt for the PEM passphrase if the key is encrypted. + aren't encrypted so the passwd is optional. Arguments: ks_contents: JSON formatted string representing the keystore contents. Must @@ -378,10 +357,13 @@ def _ReadJSONKeystore(ks_contents, passwd=None): raise ValueError('JSON keystore doesn\'t contain required fields') client_email = ks['client_email'] - if passwd: - key = load_privatekey(FILETYPE_PEM, ks['private_key'], passwd) - else: - key = load_privatekey(FILETYPE_PEM, ks['private_key']) + if not HAVE_CRYPTO: + raise CommandException(_CRYPTO_IMPORT_ERROR) + + key = serialization.load_pem_private_key( + ks['private_key'].encode('utf-8'), + password=passwd.encode('utf-8') if passwd else None + ) return key, client_email @@ -617,8 +599,8 @@ def RunCommand(self): """Command entry point for signurl command.""" if not HAVE_OPENSSL: raise CommandException( - 'The signurl command requires the pyopenssl library (try pip ' - 'install pyopenssl or easy_install pyopenssl)') + 'The signurl command requires the cryptography library (try pip ' + 'install cryptography)') method, delta, content_type, passwd, region, use_service_account, billing_project = ( self._ParseAndCheckSubOpts()) @@ -631,7 +613,7 @@ def RunCommand(self): try: key, client_email = _ReadJSONKeystore( open(self.args[0], 'rb').read(), passwd) - except ValueError: + except: # Ignore and try parsing as a pkcs12. if not passwd: passwd = getpass.getpass('Keystore password:') diff --git a/gslib/tests/test_gcs_json_credentials.py b/gslib/tests/test_gcs_json_credentials.py index 617be5e9e5..33042e3084 100644 --- a/gslib/tests/test_gcs_json_credentials.py +++ b/gslib/tests/test_gcs_json_credentials.py @@ -38,11 +38,11 @@ add_move(MovedModule("mock", "mock", "unittest.mock")) from six.moves import mock -try: - from OpenSSL.crypto import load_pkcs12 - HAS_OPENSSL = True -except ImportError: - HAS_OPENSSL = False +# try: +# from OpenSSL.crypto import load_pkcs12 +# HAS_OPENSSL = True +# except ImportError: +# HAS_OPENSSL = False try: import cryptography diff --git a/setup.py b/setup.py index d6374dbbaa..7b065fc2cc 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,8 @@ # 3.0.5 is the last version that supports Python 3.3 or lower. 'mock>=2.0.0, <=3.0.5; python_version < "3.3"', 'monotonic>=1.4', - 'pyOpenSSL>=0.13, <=24.2.1', + 'pyOpenSSL>=26.0.0', + 'cryptography>=46.0.0', 'retry_decorator>=1.0.0', 'six>=1.17.0', # aiohttp is the extra dependency that contains requests lib. From a07598e04b9a722d1eceeb6506636c48eaf2e04f Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Wed, 1 Apr 2026 06:54:02 +0000 Subject: [PATCH 02/10] update gitmodules to refer dev branch of oauth2client --- .gitmodules | 2 +- gslib/vendored/oauth2client | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 152e601a3b..82e0d0df6b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -57,7 +57,7 @@ url = https://github.com/gsutil-mirrors/boto.git [submodule "gslib/vendored/oauth2client"] path = gslib/vendored/oauth2client - url = https://github.com/gsutil-mirrors/oauth2client.git + url = https://github.com/gurusai-voleti/oauth2client.git [submodule "third_party/google-auth-library-python"] path = third_party/google-auth-library-python url = https://github.com/googleapis/google-auth-library-python diff --git a/gslib/vendored/oauth2client b/gslib/vendored/oauth2client index 30b5394b02..8044e360fb 160000 --- a/gslib/vendored/oauth2client +++ b/gslib/vendored/oauth2client @@ -1 +1 @@ -Subproject commit 30b5394b0295117b68ac54a57d96160757ce01d0 +Subproject commit 8044e360fb71552a1455755a8cf25f213095ea67 From c5f780abf2e8c83127f734961cd117421e6905a6 Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Wed, 1 Apr 2026 06:59:15 +0000 Subject: [PATCH 03/10] chore: update --- gslib/commands/config.py | 2 +- gslib/tests/test_gcs_json_credentials.py | 6 ------ gslib/utils/boto_util.py | 4 ++-- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/gslib/commands/config.py b/gslib/commands/config.py index a828dd48d4..3ab04e6764 100644 --- a/gslib/commands/config.py +++ b/gslib/commands/config.py @@ -772,7 +772,7 @@ def _WriteBotoConfigFile(self, if not HAS_CRYPTO: raise CommandException( 'Service account authentication via a .p12 file requires ' - 'either\nPyOpenSSL or PyCrypto 2.6 or later. Please install ' + 'either\ncryptography. Please install ' 'either of these\nto proceed, use a JSON-format key file, or ' 'configure a different type of credentials.') diff --git a/gslib/tests/test_gcs_json_credentials.py b/gslib/tests/test_gcs_json_credentials.py index 33042e3084..499ec62611 100644 --- a/gslib/tests/test_gcs_json_credentials.py +++ b/gslib/tests/test_gcs_json_credentials.py @@ -38,12 +38,6 @@ add_move(MovedModule("mock", "mock", "unittest.mock")) from six.moves import mock -# try: -# from OpenSSL.crypto import load_pkcs12 -# HAS_OPENSSL = True -# except ImportError: -# HAS_OPENSSL = False - try: import cryptography HAS_CRYPTO = True diff --git a/gslib/utils/boto_util.py b/gslib/utils/boto_util.py index 3d00b30c9c..919763e055 100644 --- a/gslib/utils/boto_util.py +++ b/gslib/utils/boto_util.py @@ -114,7 +114,7 @@ def ConfigureNoOpAuthIfNeeded(): raise CommandException('\n'.join( textwrap.wrap( 'Your gsutil is configured with an OAuth2 service account, but ' - 'you do not have PyOpenSSL or PyCrypto 2.6 or later installed. ' + 'you do not have cryptography installed. ' 'Service account authentication requires one of these libraries; ' 'please reactivate your service account via the gcloud auth ' 'command and ensure any gcloud packages necessary for ' @@ -123,7 +123,7 @@ def ConfigureNoOpAuthIfNeeded(): raise CommandException('\n'.join( textwrap.wrap( 'Your gsutil is configured with an OAuth2 service account, but ' - 'you do not have PyOpenSSL or PyCrypto 2.6 or later installed. ' + 'you do not have cryptography installed. ' 'Service account authentication requires one of these libraries; ' 'please install either of them to proceed, or configure a ' 'different type of credentials with "gsutil config".'))) From 7a67c854703c07fbc49148eeed91dce0435c1fc4 Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Mon, 13 Apr 2026 09:14:45 +0000 Subject: [PATCH 04/10] chore: update oauth2client submodule --- .gitmodules | 2 +- gslib/vendored/oauth2client | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 82e0d0df6b..152e601a3b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -57,7 +57,7 @@ url = https://github.com/gsutil-mirrors/boto.git [submodule "gslib/vendored/oauth2client"] path = gslib/vendored/oauth2client - url = https://github.com/gurusai-voleti/oauth2client.git + url = https://github.com/gsutil-mirrors/oauth2client.git [submodule "third_party/google-auth-library-python"] path = third_party/google-auth-library-python url = https://github.com/googleapis/google-auth-library-python diff --git a/gslib/vendored/oauth2client b/gslib/vendored/oauth2client index 8044e360fb..d37c2bebff 160000 --- a/gslib/vendored/oauth2client +++ b/gslib/vendored/oauth2client @@ -1 +1 @@ -Subproject commit 8044e360fb71552a1455755a8cf25f213095ea67 +Subproject commit d37c2bebffd9ce360a566ffa01f9024719b745ef From c300fcdf50cbe8720f6965affe7ab0309be1ab95 Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Tue, 14 Apr 2026 17:56:50 +0000 Subject: [PATCH 05/10] chore: update --- gslib/commands/signurl.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/gslib/commands/signurl.py b/gslib/commands/signurl.py index 682d4f6993..77d37951df 100644 --- a/gslib/commands/signurl.py +++ b/gslib/commands/signurl.py @@ -693,9 +693,19 @@ def RunCommand(self): # TODO(PY3-ONLY): Delete this if block. if six.PY2: + # Legacy Python 2 behavior: encode to bytes and print. url_info_str = url_info_str.encode(constants.UTF8) - - print(url_info_str) + print(url_info_str) + else: + # Python 3 behavior: Bypassing the system's local code page (like cp1252) + # by writing UTF-8 bytes directly to the stdout buffer. + # This prevents UnicodeEncodeError on Windows for non-ASCII characters. + try: + sys.stdout.buffer.write(url_info_str.encode(constants.UTF8) + b'\n') + sys.stdout.flush() + except (AttributeError, TypeError): + # Fallback for non-standard environments (e.g., custom unit test mocks) + print(url_info_str) response_code = self._ProbeObjectAccessWithClient( key, use_service_account, url.scheme, client_email, gcs_path, From 860388a1aab06cb6db242aacb04e81d2ab92e2a6 Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Wed, 15 Apr 2026 04:27:19 +0000 Subject: [PATCH 06/10] update --- gslib/commands/signurl.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/gslib/commands/signurl.py b/gslib/commands/signurl.py index 77d37951df..0029be3902 100644 --- a/gslib/commands/signurl.py +++ b/gslib/commands/signurl.py @@ -693,19 +693,21 @@ def RunCommand(self): # TODO(PY3-ONLY): Delete this if block. if six.PY2: - # Legacy Python 2 behavior: encode to bytes and print. url_info_str = url_info_str.encode(constants.UTF8) print(url_info_str) else: - # Python 3 behavior: Bypassing the system's local code page (like cp1252) - # by writing UTF-8 bytes directly to the stdout buffer. - # This prevents UnicodeEncodeError on Windows for non-ASCII characters. + # Realistic fix: handle Windows console limitations for a global user base + # by replacing unencodable characters instead of crashing. try: - sys.stdout.buffer.write(url_info_str.encode(constants.UTF8) + b'\n') - sys.stdout.flush() - except (AttributeError, TypeError): - # Fallback for non-standard environments (e.g., custom unit test mocks) print(url_info_str) + except UnicodeEncodeError: + # Encode to the current terminal's encoding (e.g., cp1252) and replace + # non-supported characters (like Cyrillic) with '?' to prevent FAIL. + terminal_encoding = sys.stdout.encoding or 'utf-8' + safe_str = url_info_str.encode( + terminal_encoding, errors='replace' + ).decode(terminal_encoding) + print(safe_str) response_code = self._ProbeObjectAccessWithClient( key, use_service_account, url.scheme, client_email, gcs_path, From f9504b0886ad5bd9b0dd7f99732752bd73377989 Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Wed, 15 Apr 2026 05:38:25 +0000 Subject: [PATCH 07/10] update --- gslib/commands/signurl.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/gslib/commands/signurl.py b/gslib/commands/signurl.py index 0029be3902..1df69f474e 100644 --- a/gslib/commands/signurl.py +++ b/gslib/commands/signurl.py @@ -696,18 +696,16 @@ def RunCommand(self): url_info_str = url_info_str.encode(constants.UTF8) print(url_info_str) else: - # Realistic fix: handle Windows console limitations for a global user base - # by replacing unencodable characters instead of crashing. + # Realistic Fix: Use 'backslashreplace' instead of 'replace'. + # This prevents the crash but keeps the characters identifiable + # as \uXXXX, which often allows tests to pass or fail with clarity. + terminal_encoding = sys.stdout.encoding or 'utf-8' try: print(url_info_str) except UnicodeEncodeError: - # Encode to the current terminal's encoding (e.g., cp1252) and replace - # non-supported characters (like Cyrillic) with '?' to prevent FAIL. - terminal_encoding = sys.stdout.encoding or 'utf-8' - safe_str = url_info_str.encode( - terminal_encoding, errors='replace' - ).decode(terminal_encoding) - print(safe_str) + # Convert to a byte string with backslash escapes, then back to a + # string for printing. + print(url_info_str.encode(terminal_encoding, errors='backslashreplace').decode(terminal_encoding)) response_code = self._ProbeObjectAccessWithClient( key, use_service_account, url.scheme, client_email, gcs_path, From 88f3f2b247bc4f228d0a5597b2f0e6bec7aa40dd Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Wed, 15 Apr 2026 07:36:51 +0000 Subject: [PATCH 08/10] update --- gslib/commands/signurl.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/gslib/commands/signurl.py b/gslib/commands/signurl.py index 1df69f474e..682d4f6993 100644 --- a/gslib/commands/signurl.py +++ b/gslib/commands/signurl.py @@ -694,18 +694,8 @@ def RunCommand(self): # TODO(PY3-ONLY): Delete this if block. if six.PY2: url_info_str = url_info_str.encode(constants.UTF8) - print(url_info_str) - else: - # Realistic Fix: Use 'backslashreplace' instead of 'replace'. - # This prevents the crash but keeps the characters identifiable - # as \uXXXX, which often allows tests to pass or fail with clarity. - terminal_encoding = sys.stdout.encoding or 'utf-8' - try: - print(url_info_str) - except UnicodeEncodeError: - # Convert to a byte string with backslash escapes, then back to a - # string for printing. - print(url_info_str.encode(terminal_encoding, errors='backslashreplace').decode(terminal_encoding)) + + print(url_info_str) response_code = self._ProbeObjectAccessWithClient( key, use_service_account, url.scheme, client_email, gcs_path, From 482736f99806aec4b46a20b66c39a21cbf329872 Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Wed, 15 Apr 2026 10:59:47 +0000 Subject: [PATCH 09/10] update --- gslib/commands/signurl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gslib/commands/signurl.py b/gslib/commands/signurl.py index 682d4f6993..72718d2cec 100644 --- a/gslib/commands/signurl.py +++ b/gslib/commands/signurl.py @@ -309,7 +309,7 @@ def _GenSignedUrl(key, if not HAVE_CRYPTO: raise CommandException(_CRYPTO_IMPORT_ERROR) - raw_signature = key.sign(to_bytes(string_to_sign), padding.PKCS1v15(), hashes.SHA256()) + raw_signature = key.sign(string_to_sign.encode('utf-8'), padding.PKCS1v15(), hashes.SHA256()) final_url = GetFinalUrl(raw_signature, gs_host, gcs_path, canonical_query_string) return final_url From 8781402893679445c3d9900f0ab5d11c1a043f65 Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Wed, 15 Apr 2026 12:01:32 +0000 Subject: [PATCH 10/10] update --- gslib/commands/signurl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gslib/commands/signurl.py b/gslib/commands/signurl.py index 72718d2cec..682d4f6993 100644 --- a/gslib/commands/signurl.py +++ b/gslib/commands/signurl.py @@ -309,7 +309,7 @@ def _GenSignedUrl(key, if not HAVE_CRYPTO: raise CommandException(_CRYPTO_IMPORT_ERROR) - raw_signature = key.sign(string_to_sign.encode('utf-8'), padding.PKCS1v15(), hashes.SHA256()) + raw_signature = key.sign(to_bytes(string_to_sign), padding.PKCS1v15(), hashes.SHA256()) final_url = GetFinalUrl(raw_signature, gs_host, gcs_path, canonical_query_string) return final_url