Skip to content

Commit b82cd7f

Browse files
authored
SNOW-843716: cryptography dep cleanup (#1779)
1 parent 186a186 commit b82cd7f

File tree

9 files changed

+51
-188
lines changed

9 files changed

+51
-188
lines changed

DESCRIPTION.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
88

99
# Release Notes
1010

11-
- v3.3.2(TBD)
11+
- v3.4.0(TBD)
1212

1313
- Added support for `use_logical_type` in `write_pandas`.
14+
- Removed dependencies on pycryptodomex and oscrypto. All connections now go through OpenSSL via the cryptography library, which was already a dependency.
1415

1516
- v3.3.1(October 16,2023)
1617

ci/test_fips_docker.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ user_id=$(id -u $USER)
2121
docker run --network=host \
2222
-e LANG=en_US.UTF-8 \
2323
-e TERM=vt102 \
24-
-e SF_USE_OPENSSL_ONLY=True \
2524
-e PIP_DISABLE_PIP_VERSION_CHECK=1 \
2625
-e LOCAL_USER_ID=$user_id \
2726
-e CRYPTOGRAPHY_ALLOW_OPENSSL_102=1 \

ci/test_lambda_docker.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ user_id=$(id -u $USER)
2525
docker run --network=host \
2626
-e LANG=en_US.UTF-8 \
2727
-e TERM=vt102 \
28-
-e SF_USE_OPENSSL_ONLY=True \
2928
-e PIP_DISABLE_PIP_VERSION_CHECK=1 \
3029
-e LOCAL_USER_ID=$user_id \
3130
-e CRYPTOGRAPHY_ALLOW_OPENSSL_102=1 \

setup.cfg

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ install_requires =
4545
asn1crypto>0.24.0,<2.0.0
4646
cffi>=1.9,<2.0.0
4747
cryptography>=3.1.0,<42.0.0
48-
oscrypto<2.0.0
4948
pyOpenSSL>=16.2.0,<24.0.0
50-
pycryptodomex!=3.5.0,>=3.2,<4.0.0
5149
pyjwt<3.0.0
5250
pytz
5351
requests<3.0.0

src/snowflake/connector/connection.py

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,9 @@ def DefaultConverterClass() -> type:
189189
"client_store_temporary_credential": (False, bool),
190190
"client_request_mfa_token": (False, bool),
191191
"use_openssl_only": (
192-
False,
192+
True,
193193
bool,
194-
), # only use openssl instead of python only crypto modules
194+
), # ignored - python only crypto modules are no longer used
195195
# whether to convert Arrow number values to decimal instead of doubles
196196
"arrow_number_to_decimal": (False, bool),
197197
"enable_stage_s3_privatelink_for_us_east_1": (
@@ -287,7 +287,6 @@ class SnowflakeConnection:
287287
validate_default_parameters: Validate database, schema, role and warehouse used on Snowflake.
288288
is_pyformat: Whether the current argument binding is pyformat or format.
289289
consent_cache_id_token: Consented cache ID token.
290-
use_openssl_only: Use OpenSSL instead of pure Python libraries for signature verification and encryption.
291290
enable_stage_s3_privatelink_for_us_east_1: when true, clients use regional s3 url to upload files.
292291
enable_connection_diag: when true, clients will generate a connectivity diagnostic report.
293292
connection_diag_log_path: path to location to create diag report with enable_connection_diag.
@@ -574,7 +573,8 @@ def disable_request_pooling(self, value) -> None:
574573

575574
@property
576575
def use_openssl_only(self) -> bool:
577-
return self._use_openssl_only
576+
# Deprecated, kept for backwards compatibility
577+
return True
578578

579579
@property
580580
def arrow_number_to_decimal(self):
@@ -1117,19 +1117,6 @@ def __config(self, **kwargs):
11171117
"CHECKED."
11181118
)
11191119

1120-
if "SF_USE_OPENSSL_ONLY" not in os.environ:
1121-
logger.info("Setting use_openssl_only mode to %s", self.use_openssl_only)
1122-
os.environ["SF_USE_OPENSSL_ONLY"] = str(self.use_openssl_only)
1123-
elif (
1124-
os.environ.get("SF_USE_OPENSSL_ONLY", "False") == "True"
1125-
) != self.use_openssl_only:
1126-
logger.warning(
1127-
"Mode use_openssl_only is already set to: %s, ignoring set request to: %s",
1128-
os.environ["SF_USE_OPENSSL_ONLY"],
1129-
self.use_openssl_only,
1130-
)
1131-
self._use_openssl_only = os.environ["SF_USE_OPENSSL_ONLY"] == "True"
1132-
11331120
def cmd_query(
11341121
self,
11351122
sql: str,

src/snowflake/connector/encryption_util.py

Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from logging import getLogger
1313
from typing import IO, TYPE_CHECKING
1414

15-
from Cryptodome.Cipher import AES
1615
from cryptography.hazmat.backends import default_backend
1716
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
1817

@@ -69,7 +68,6 @@ def encrypt_stream(
6968
The encryption metadata.
7069
"""
7170
logger = getLogger(__name__)
72-
use_openssl_only = os.getenv("SF_USE_OPENSSL_ONLY", "False") == "True"
7371
decoded_key = base64.standard_b64decode(
7472
encryption_material.query_stage_master_key
7573
)
@@ -79,14 +77,9 @@ def encrypt_stream(
7977
# Generate key for data encryption
8078
iv_data = SnowflakeEncryptionUtil.get_secure_random(block_size)
8179
file_key = SnowflakeEncryptionUtil.get_secure_random(key_size)
82-
if not use_openssl_only:
83-
data_cipher = AES.new(key=file_key, mode=AES.MODE_CBC, IV=iv_data)
84-
else:
85-
backend = default_backend()
86-
cipher = Cipher(
87-
algorithms.AES(file_key), modes.CBC(iv_data), backend=backend
88-
)
89-
encryptor = cipher.encryptor()
80+
backend = default_backend()
81+
cipher = Cipher(algorithms.AES(file_key), modes.CBC(iv_data), backend=backend)
82+
encryptor = cipher.encryptor()
9083

9184
padded = False
9285
while True:
@@ -96,30 +89,17 @@ def encrypt_stream(
9689
elif len(chunk) % block_size != 0:
9790
chunk = PKCS5_PAD(chunk, block_size)
9891
padded = True
99-
if not use_openssl_only:
100-
out.write(data_cipher.encrypt(chunk))
101-
else:
102-
out.write(encryptor.update(chunk))
92+
out.write(encryptor.update(chunk))
10393
if not padded:
104-
if not use_openssl_only:
105-
out.write(
106-
data_cipher.encrypt(block_size * chr(block_size).encode(UTF8))
107-
)
108-
else:
109-
out.write(encryptor.update(block_size * chr(block_size).encode(UTF8)))
110-
if use_openssl_only:
111-
out.write(encryptor.finalize())
94+
out.write(encryptor.update(block_size * chr(block_size).encode(UTF8)))
95+
out.write(encryptor.finalize())
11296

11397
# encrypt key with QRMK
114-
if not use_openssl_only:
115-
key_cipher = AES.new(key=decoded_key, mode=AES.MODE_ECB)
116-
enc_kek = key_cipher.encrypt(PKCS5_PAD(file_key, block_size))
117-
else:
118-
cipher = Cipher(algorithms.AES(decoded_key), modes.ECB(), backend=backend)
119-
encryptor = cipher.encryptor()
120-
enc_kek = (
121-
encryptor.update(PKCS5_PAD(file_key, block_size)) + encryptor.finalize()
122-
)
98+
cipher = Cipher(algorithms.AES(decoded_key), modes.ECB(), backend=backend)
99+
encryptor = cipher.encryptor()
100+
enc_kek = (
101+
encryptor.update(PKCS5_PAD(file_key, block_size)) + encryptor.finalize()
102+
)
123103

124104
mat_desc = MaterialDescriptor(
125105
smk_id=encryption_material.smk_id,
@@ -178,7 +158,6 @@ def decrypt_stream(
178158
) -> None:
179159
"""To read from `src` stream then decrypt to `out` stream."""
180160

181-
use_openssl_only = os.getenv("SF_USE_OPENSSL_ONLY", "False") == "True"
182161
key_base64 = metadata.key
183162
iv_base64 = metadata.iv
184163
decoded_key = base64.standard_b64decode(
@@ -187,37 +166,26 @@ def decrypt_stream(
187166
key_bytes = base64.standard_b64decode(key_base64)
188167
iv_bytes = base64.standard_b64decode(iv_base64)
189168

190-
if not use_openssl_only:
191-
key_cipher = AES.new(key=decoded_key, mode=AES.MODE_ECB)
192-
file_key = PKCS5_UNPAD(key_cipher.decrypt(key_bytes))
193-
data_cipher = AES.new(key=file_key, mode=AES.MODE_CBC, IV=iv_bytes)
194-
else:
195-
backend = default_backend()
196-
cipher = Cipher(algorithms.AES(decoded_key), modes.ECB(), backend=backend)
197-
decryptor = cipher.decryptor()
198-
file_key = PKCS5_UNPAD(decryptor.update(key_bytes) + decryptor.finalize())
199-
cipher = Cipher(
200-
algorithms.AES(file_key), modes.CBC(iv_bytes), backend=backend
201-
)
202-
decryptor = cipher.decryptor()
169+
backend = default_backend()
170+
cipher = Cipher(algorithms.AES(decoded_key), modes.ECB(), backend=backend)
171+
decryptor = cipher.decryptor()
172+
file_key = PKCS5_UNPAD(decryptor.update(key_bytes) + decryptor.finalize())
173+
cipher = Cipher(algorithms.AES(file_key), modes.CBC(iv_bytes), backend=backend)
174+
decryptor = cipher.decryptor()
203175

204176
last_decrypted_chunk = None
205177
chunk = src.read(chunk_size)
206178
while len(chunk) != 0:
207179
if last_decrypted_chunk is not None:
208180
out.write(last_decrypted_chunk)
209-
if not use_openssl_only:
210-
d = data_cipher.decrypt(chunk)
211-
else:
212-
d = decryptor.update(chunk)
181+
d = decryptor.update(chunk)
213182
last_decrypted_chunk = d
214183
chunk = src.read(chunk_size)
215184

216185
if last_decrypted_chunk is not None:
217186
offset = PKCS5_OFFSET(last_decrypted_chunk)
218187
out.write(last_decrypted_chunk[:-offset])
219-
if use_openssl_only:
220-
out.write(decryptor.finalize())
188+
out.write(decryptor.finalize())
221189

222190
@staticmethod
223191
def decrypt_file(

src/snowflake/connector/file_util.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from logging import getLogger
1414
from typing import IO
1515

16-
from Cryptodome.Hash import SHA256
1716
from cryptography.hazmat.backends import default_backend
1817
from cryptography.hazmat.primitives import hashes
1918

@@ -33,27 +32,17 @@ def get_digest_and_size(src: IO[bytes]) -> tuple[str, int]:
3332
Returns:
3433
Tuple of src's digest and src's size in bytes.
3534
"""
36-
use_openssl_only = os.getenv("SF_USE_OPENSSL_ONLY", "False") == "True"
3735
CHUNK_SIZE = 64 * kilobyte
38-
if not use_openssl_only:
39-
m = SHA256.new()
40-
else:
41-
backend = default_backend()
42-
chosen_hash = hashes.SHA256()
43-
hasher = hashes.Hash(chosen_hash, backend)
36+
backend = default_backend()
37+
chosen_hash = hashes.SHA256()
38+
hasher = hashes.Hash(chosen_hash, backend)
4439
while True:
4540
chunk = src.read(CHUNK_SIZE)
4641
if chunk == b"":
4742
break
48-
if not use_openssl_only:
49-
m.update(chunk)
50-
else:
51-
hasher.update(chunk)
52-
53-
if not use_openssl_only:
54-
digest = base64.standard_b64encode(m.digest()).decode(UTF8)
55-
else:
56-
digest = base64.standard_b64encode(hasher.finalize()).decode(UTF8)
43+
hasher.update(chunk)
44+
45+
digest = base64.standard_b64encode(hasher.finalize()).decode(UTF8)
5746

5847
size = src.tell()
5948
src.seek(0)

src/snowflake/connector/ocsp_asn1crypto.py

Lines changed: 20 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55

66
from __future__ import annotations
77

8-
import os
9-
import platform
10-
import sys
11-
import warnings
128
from base64 import b64decode, b64encode
139
from collections import OrderedDict
1410
from datetime import datetime, timezone
@@ -28,9 +24,6 @@
2824
Version,
2925
)
3026
from asn1crypto.x509 import Certificate
31-
from Cryptodome.Hash import SHA1, SHA256, SHA384, SHA512
32-
from Cryptodome.PublicKey import RSA
33-
from Cryptodome.Signature import PKCS1_v1_5
3427
from cryptography.exceptions import InvalidSignature
3528
from cryptography.hazmat.backends import default_backend
3629
from cryptography.hazmat.primitives import hashes, serialization
@@ -48,20 +41,6 @@
4841
from snowflake.connector.errors import RevocationCheckError
4942
from snowflake.connector.ocsp_snowflake import SnowflakeOCSP, generate_cache_key
5043

51-
with warnings.catch_warnings():
52-
warnings.simplefilter("ignore")
53-
# force versioned dylibs onto oscrypto ssl on catalina
54-
if sys.platform == "darwin" and platform.mac_ver()[0].startswith("10.15"):
55-
from oscrypto import _module_values, use_openssl
56-
57-
if _module_values["backend"] is None:
58-
use_openssl(
59-
libcrypto_path="/usr/lib/libcrypto.35.dylib",
60-
libssl_path="/usr/lib/libssl.35.dylib",
61-
)
62-
from oscrypto import asymmetric
63-
64-
6544
logger = getLogger(__name__)
6645

6746

@@ -70,12 +49,6 @@ class SnowflakeOCSPAsn1Crypto(SnowflakeOCSP):
7049

7150
# map signature algorithm name to digest class
7251
SIGNATURE_ALGORITHM_TO_DIGEST_CLASS = {
73-
"sha256": SHA256,
74-
"sha384": SHA384,
75-
"sha512": SHA512,
76-
}
77-
78-
SIGNATURE_ALGORITHM_TO_DIGEST_CLASS_OPENSSL = {
7952
"sha256": hashes.SHA256,
8053
"sha384": hashes.SHA3_384,
8154
"sha512": hashes.SHA3_512,
@@ -378,51 +351,29 @@ def process_ocsp_response(self, issuer, cert_id, ocsp_response):
378351
raise RevocationCheckError(msg=debug_msg, errno=op_er.errno)
379352

380353
def verify_signature(self, signature_algorithm, signature, cert, data):
381-
use_openssl_only = os.getenv("SF_USE_OPENSSL_ONLY", "False") == "True"
382-
if not use_openssl_only:
383-
pubkey = asymmetric.load_public_key(cert.public_key).unwrap().dump()
384-
rsakey = RSA.importKey(pubkey)
385-
signer = PKCS1_v1_5.new(rsakey)
386-
if (
354+
backend = default_backend()
355+
public_key = serialization.load_der_public_key(
356+
cert.public_key.dump(), backend=default_backend()
357+
)
358+
if (
359+
signature_algorithm
360+
in SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS
361+
):
362+
chosen_hash = SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS[
387363
signature_algorithm
388-
in SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS
389-
):
390-
digest = SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS[
391-
signature_algorithm
392-
].new()
393-
else:
394-
# the last resort. should not happen.
395-
digest = SHA1.new()
396-
digest.update(data.dump())
397-
if not signer.verify(digest, signature):
398-
raise RevocationCheckError(msg="Failed to verify the signature")
399-
364+
]()
400365
else:
401-
backend = default_backend()
402-
public_key = serialization.load_der_public_key(
403-
cert.public_key.dump(), backend=default_backend()
366+
# the last resort. should not happen.
367+
chosen_hash = hashes.SHA1()
368+
hasher = hashes.Hash(chosen_hash, backend)
369+
hasher.update(data.dump())
370+
digest = hasher.finalize()
371+
try:
372+
public_key.verify(
373+
signature, digest, padding.PKCS1v15(), utils.Prehashed(chosen_hash)
404374
)
405-
if (
406-
signature_algorithm
407-
in SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS
408-
):
409-
chosen_hash = (
410-
SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS_OPENSSL[
411-
signature_algorithm
412-
]()
413-
)
414-
else:
415-
# the last resort. should not happen.
416-
chosen_hash = hashes.SHA1()
417-
hasher = hashes.Hash(chosen_hash, backend)
418-
hasher.update(data.dump())
419-
digest = hasher.finalize()
420-
try:
421-
public_key.verify(
422-
signature, digest, padding.PKCS1v15(), utils.Prehashed(chosen_hash)
423-
)
424-
except InvalidSignature:
425-
raise RevocationCheckError(msg="Failed to verify the signature")
375+
except InvalidSignature:
376+
raise RevocationCheckError(msg="Failed to verify the signature")
426377

427378
def extract_certificate_chain(
428379
self, connection: Connection

0 commit comments

Comments
 (0)