Skip to content

Commit 6910dc5

Browse files
committed
Remove dependencies on Cryptodome and oscrypto
The code to use cryptography (which uses OpenSSL) already existed, but it just wasn't being used by default. Since cryptography is currently a mandatory dependency, we may as well use it all the time. Partially resolves snowflakedb#1605/SNOW-843716.
1 parent 51749d2 commit 6910dc5

8 files changed

+58
-181
lines changed

DESCRIPTION.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
1414
- Improved OCSP response caching to remove tmp cache files on Windows.
1515
- Added a parameter `server_session_keep_alive` in `SnowflakeConnection` that skips session deletion when client connection closes.
1616
- Tightened our pinning of platformdirs, to prevent their new releases breaking us.
17+
- Remove dependencies on Cryptodome and oscrypto and remove the `use_openssl_only` parameter. All connections now go through OpenSSL via the cryptography library, which was already a dependency.
1718

1819
- v3.0.4(May 23,2023)
1920
- Fixed a bug in which `cursor.execute()` could modify the argument statement_params dictionary object when executing a multistatement query.

ci/test_fips_docker.sh

-1
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 \

setup.cfg

-2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ install_requires =
4646
asn1crypto>0.24.0,<2.0.0
4747
cffi>=1.9,<2.0.0
4848
cryptography>=3.1.0,<42.0.0
49-
oscrypto<2.0.0
5049
pyOpenSSL>=16.2.0,<24.0.0
51-
pycryptodomex!=3.5.0,>=3.2,<4.0.0
5250
pyjwt<3.0.0
5351
pytz
5452
requests<3.0.0

src/snowflake/connector/connection.py

+4-17
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,9 @@ def DefaultConverterClass() -> type:
182182
"client_store_temporary_credential": (False, bool),
183183
"client_request_mfa_token": (False, bool),
184184
"use_openssl_only": (
185-
False,
185+
True,
186186
bool,
187-
), # only use openssl instead of python only crypto modules
187+
), # ignored - python only crypto modules are no longer used
188188
# whether to convert Arrow number values to decimal instead of doubles
189189
"arrow_number_to_decimal": (False, bool),
190190
"enable_stage_s3_privatelink_for_us_east_1": (
@@ -276,7 +276,6 @@ class SnowflakeConnection:
276276
validate_default_parameters: Validate database, schema, role and warehouse used on Snowflake.
277277
is_pyformat: Whether the current argument binding is pyformat or format.
278278
consent_cache_id_token: Consented cache ID token.
279-
use_openssl_only: Use OpenSSL instead of pure Python libraries for signature verification and encryption.
280279
enable_stage_s3_privatelink_for_us_east_1: when true, clients use regional s3 url to upload files.
281280
enable_connection_diag: when true, clients will generate a connectivity diagnostic report.
282281
connection_diag_log_path: path to location to create diag report with enable_connection_diag.
@@ -535,7 +534,8 @@ def disable_request_pooling(self, value) -> None:
535534

536535
@property
537536
def use_openssl_only(self) -> bool:
538-
return self._use_openssl_only
537+
# Deprecated, kept for backwards compatibility
538+
return True
539539

540540
@property
541541
def arrow_number_to_decimal(self):
@@ -1054,19 +1054,6 @@ def __config(self, **kwargs):
10541054
"CHECKED."
10551055
)
10561056

1057-
if "SF_USE_OPENSSL_ONLY" not in os.environ:
1058-
logger.info("Setting use_openssl_only mode to %s", self.use_openssl_only)
1059-
os.environ["SF_USE_OPENSSL_ONLY"] = str(self.use_openssl_only)
1060-
elif (
1061-
os.environ.get("SF_USE_OPENSSL_ONLY", "False") == "True"
1062-
) != self.use_openssl_only:
1063-
logger.warning(
1064-
"Mode use_openssl_only is already set to: %s, ignoring set request to: %s",
1065-
os.environ["SF_USE_OPENSSL_ONLY"],
1066-
self.use_openssl_only,
1067-
)
1068-
self._use_openssl_only = os.environ["SF_USE_OPENSSL_ONLY"] == "True"
1069-
10701057
def cmd_query(
10711058
self,
10721059
sql: str,

src/snowflake/connector/encryption_util.py

+25-51
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,11 @@ 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(
82+
algorithms.AES(file_key), modes.CBC(iv_data), backend=backend
83+
)
84+
encryptor = cipher.encryptor()
9085

9186
padded = False
9287
while True:
@@ -96,30 +91,19 @@ def encrypt_stream(
9691
elif len(chunk) % block_size != 0:
9792
chunk = PKCS5_PAD(chunk, block_size)
9893
padded = True
99-
if not use_openssl_only:
100-
out.write(data_cipher.encrypt(chunk))
101-
else:
102-
out.write(encryptor.update(chunk))
94+
out.write(encryptor.update(chunk))
10395
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())
96+
out.write(
97+
data_cipher.encrypt(block_size * chr(block_size).encode(UTF8))
98+
)
99+
out.write(encryptor.finalize())
112100

113101
# 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-
)
102+
cipher = Cipher(algorithms.AES(decoded_key), modes.ECB(), backend=backend)
103+
encryptor = cipher.encryptor()
104+
enc_kek = (
105+
encryptor.update(PKCS5_PAD(file_key, block_size)) + encryptor.finalize()
106+
)
123107

124108
mat_desc = MaterialDescriptor(
125109
smk_id=encryption_material.smk_id,
@@ -178,7 +162,6 @@ def decrypt_stream(
178162
) -> None:
179163
"""To read from `src` stream then decrypt to `out` stream."""
180164

181-
use_openssl_only = os.getenv("SF_USE_OPENSSL_ONLY", "False") == "True"
182165
key_base64 = metadata.key
183166
iv_base64 = metadata.iv
184167
decoded_key = base64.standard_b64decode(
@@ -187,37 +170,28 @@ def decrypt_stream(
187170
key_bytes = base64.standard_b64decode(key_base64)
188171
iv_bytes = base64.standard_b64decode(iv_base64)
189172

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()
173+
backend = default_backend()
174+
cipher = Cipher(algorithms.AES(decoded_key), modes.ECB(), backend=backend)
175+
decryptor = cipher.decryptor()
176+
file_key = PKCS5_UNPAD(decryptor.update(key_bytes) + decryptor.finalize())
177+
cipher = Cipher(
178+
algorithms.AES(file_key), modes.CBC(iv_bytes), backend=backend
179+
)
180+
decryptor = cipher.decryptor()
203181

204182
last_decrypted_chunk = None
205183
chunk = src.read(chunk_size)
206184
while len(chunk) != 0:
207185
if last_decrypted_chunk is not None:
208186
out.write(last_decrypted_chunk)
209-
if not use_openssl_only:
210-
d = data_cipher.decrypt(chunk)
211-
else:
212-
d = decryptor.update(chunk)
187+
d = decryptor.update(chunk)
213188
last_decrypted_chunk = d
214189
chunk = src.read(chunk_size)
215190

216191
if last_decrypted_chunk is not None:
217192
offset = PKCS5_OFFSET(last_decrypted_chunk)
218193
out.write(last_decrypted_chunk[:-offset])
219-
if use_openssl_only:
220-
out.write(decryptor.finalize())
194+
out.write(decryptor.finalize())
221195

222196
@staticmethod
223197
def decrypt_file(

src/snowflake/connector/file_util.py

+6-17
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

+22-64
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@
2828
Version,
2929
)
3030
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
3431
from cryptography.exceptions import InvalidSignature
3532
from cryptography.hazmat.backends import default_backend
3633
from cryptography.hazmat.primitives import hashes, serialization
@@ -48,19 +45,6 @@
4845
from snowflake.connector.errors import RevocationCheckError
4946
from snowflake.connector.ocsp_snowflake import SnowflakeOCSP, generate_cache_key
5047

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-
6448

6549
logger = getLogger(__name__)
6650

@@ -70,12 +54,6 @@ class SnowflakeOCSPAsn1Crypto(SnowflakeOCSP):
7054

7155
# map signature algorithm name to digest class
7256
SIGNATURE_ALGORITHM_TO_DIGEST_CLASS = {
73-
"sha256": SHA256,
74-
"sha384": SHA384,
75-
"sha512": SHA512,
76-
}
77-
78-
SIGNATURE_ALGORITHM_TO_DIGEST_CLASS_OPENSSL = {
7957
"sha256": hashes.SHA256,
8058
"sha384": hashes.SHA3_384,
8159
"sha512": hashes.SHA3_512,
@@ -379,51 +357,31 @@ def process_ocsp_response(self, issuer, cert_id, ocsp_response):
379357
raise RevocationCheckError(msg=debug_msg, errno=op_er.errno)
380358

381359
def verify_signature(self, signature_algorithm, signature, cert, data):
382-
use_openssl_only = os.getenv("SF_USE_OPENSSL_ONLY", "False") == "True"
383-
if not use_openssl_only:
384-
pubkey = asymmetric.load_public_key(cert.public_key).unwrap().dump()
385-
rsakey = RSA.importKey(pubkey)
386-
signer = PKCS1_v1_5.new(rsakey)
387-
if (
388-
signature_algorithm
389-
in SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS
390-
):
391-
digest = SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS[
360+
backend = default_backend()
361+
public_key = serialization.load_der_public_key(
362+
cert.public_key.dump(), backend=default_backend()
363+
)
364+
if (
365+
signature_algorithm
366+
in SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS
367+
):
368+
chosen_hash = (
369+
SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS[
392370
signature_algorithm
393-
].new()
394-
else:
395-
# the last resort. should not happen.
396-
digest = SHA1.new()
397-
digest.update(data.dump())
398-
if not signer.verify(digest, signature):
399-
raise RevocationCheckError(msg="Failed to verify the signature")
400-
371+
]()
372+
)
401373
else:
402-
backend = default_backend()
403-
public_key = serialization.load_der_public_key(
404-
cert.public_key.dump(), backend=default_backend()
374+
# the last resort. should not happen.
375+
chosen_hash = hashes.SHA1()
376+
hasher = hashes.Hash(chosen_hash, backend)
377+
hasher.update(data.dump())
378+
digest = hasher.finalize()
379+
try:
380+
public_key.verify(
381+
signature, digest, padding.PKCS1v15(), utils.Prehashed(chosen_hash)
405382
)
406-
if (
407-
signature_algorithm
408-
in SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS
409-
):
410-
chosen_hash = (
411-
SnowflakeOCSPAsn1Crypto.SIGNATURE_ALGORITHM_TO_DIGEST_CLASS_OPENSSL[
412-
signature_algorithm
413-
]()
414-
)
415-
else:
416-
# the last resort. should not happen.
417-
chosen_hash = hashes.SHA1()
418-
hasher = hashes.Hash(chosen_hash, backend)
419-
hasher.update(data.dump())
420-
digest = hasher.finalize()
421-
try:
422-
public_key.verify(
423-
signature, digest, padding.PKCS1v15(), utils.Prehashed(chosen_hash)
424-
)
425-
except InvalidSignature:
426-
raise RevocationCheckError(msg="Failed to verify the signature")
383+
except InvalidSignature:
384+
raise RevocationCheckError(msg="Failed to verify the signature")
427385

428386
def extract_certificate_chain(
429387
self, connection: Connection

0 commit comments

Comments
 (0)