A ValueError occurs during SSLContext creation if only `ssl_verify_identity` is enabled
PyMySQL: ValueError occurs when using ssl_verify_identity=True on its own
Link: https://github.com/PyMySQL/PyMySQL/issues/1281
Background
SceneMakerAI’s FastAPI reverse proxy and management scripts use PyMySQL to connect to MariaDB 10.11 / 11.x. While reviewing TLS options, I discovered that if ssl_verify_identity=True—which the documentation merely states “verifies the server’s identity”—is set alongside ssl_ca, a Connection.__init__ error occurs in ValueError before the connection attempt even begins. It only works when ssl_verify_cert=True is also set.
- Affected: PyMySQL/PyMySQL
main31126a3 (v1.2.3 and later); identical code since v1.0.0 - Type: Issue (requires maintainer’s judgment on how to fix). A single-line fix and tests have been left in the local branch
fix-ssl-verify-identity-implies-verify-certwithout being committed
Cause
In lines pymysql/connections.py 280–286, when converting the ssl_* argument to an internal dict, if ssl_verify_cert is the default value None, it sets verify_mode to False. On the other hand, since check_hostname is bool(ssl_verify_identity), it becomes True.
_create_ssl_ctx uses this dict to set ctx.check_hostname = True on line 395 and ctx.verify_mode = ssl.CERT_NONE on line 400, respectively; however, Python ssl.SSLContext does not allow this combination and raises the following exception:
ValueError: Cannot set verify_mode to CERT_NONE when check_hostname is enabled.
The existing tests (pymysql/tests/test_connection.py lines 751–752, lines 774–775) handle the same combination of arguments but use the mock.Mock context; as a result, they were actually asserting as correct an impossible state (check_hostname true + CERT_NONE). Therefore, this issue did not surface after the option was added in #903 in 2020.
Reproduction Steps
This can be reproduced without a server. In the defer_connect=True context, only an SSLContext is created without opening a socket.
import pymysql
conn = pymysql.connect(
host="db.example.com",
ssl_ca="/etc/pki/tls/certs/ca-bundle.crt",
ssl_verify_identity=True,
defer_connect=True,
)
- Expected: Create a context where
CERT_REQUIRED+check_hostname=True. This is equivalent to the MySQL client’s--ssl-mode=VERIFY_IDENTITYcontainingVERIFY_CA. - Actual: The
ValueErrormentioned above
Proposed Fix
When ssl_verify_cert is None, set the default value of verify_mode to bool(ssl_verify_identity) instead of False (line 1). Tests: Added 1 case that uses the actual ssl.SSLContext; modified 2 existing mock assertions to use CERT_REQUIRED.
Verification
- Before the fix, unpack HEAD separately as
git archiveand run the reproduction script: confirmed thatValueErroroccurs - First added the new test to verify that it fails with the same
ValueError, then made the fix, after which it passed - Since
test_connection.pyconnects to the database at import time, pytest collection is not possible without a server. Ran only the four SSL option tests using a harness that stubspymysql.connectin the import section: all four passed - 5 test files that do not require a server passed 26 tests; 4
converters.pydoctests passed; no changes toruff format --diff/ruff check --diff - Tests that could not be run: the entire test suite requiring a production server, and the actual TLS handshake