skip to content

What does setting ssl.SSLContext.verify_mode to ssl.CERT_NONE actually give up?

level: juniorimportance: must knowfreq 60%

answer

  1. Encryption is not identity
  2. One constant removes a whole check
  3. Python refuses one order of assignment
  4. check_hostname must go first
  5. ssl.CERT_NONE means no trust anchor check

basics

~10 s

It turns off certificate verification, so a Python client accepts any certificate at all, including one an attacker generated. The connection stays encrypted but is no longer authenticated, which defeats the point of TLS.

solid answer

~40 s

`ssl.SSLContext.verify_mode` decides what Python does with the certificate the peer presents. `ssl.CERT_REQUIRED` validates it against the context trust anchors; `ssl.CERT_NONE` skips that entirely. A client with `ssl.CERT_NONE` still negotiates keys and encrypts bytes, but it has no idea who it encrypted them to: anyone who can sit in the network path can terminate the connection with a self-signed certificate, read and rewrite everything, and re-encrypt onward. Encryption without authentication buys nothing against an active attacker. Python makes the mistake awkward on purpose: assigning `ssl.CERT_NONE` while `check_hostname` is still `True` raises `ValueError`, so the code has to disable hostname checking first. On a *server* context, `ssl.CERT_NONE` is the normal setting and just means the server does not ask the client for a certificate.

code

python · 13 lines
python
import ssl

ctx = ssl.create_default_context()
print(ctx.check_hostname, ctx.verify_mode.name)  # True CERT_REQUIRED

try:
    ctx.verify_mode = ssl.CERT_NONE
except ValueError as exc:
    print("refused:", exc)

ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
print(ctx.check_hostname, ctx.verify_mode.name)  # False CERT_NONE - now unauthenticated

go deeper

for a junior

Be ready to say in one sentence that ssl.CERT_NONE stops the client checking who it is talking to, and that encrypted-but-unverified is not secure. Knowing the right fix is to add the CA, not disable the check, is enough here.

for a middle

Explain the mechanics: verify_mode covers chain validation, check_hostname covers name matching, and Python raises ValueError if you disable one without the other. Be able to name the correct fix for each error people are usually silencing.

for a senior

An interviewer expects you to catch this in review and to trace where it came from - a private CA, an expired certificate, an IP address dialled instead of a name - and to fix the cause. Expect to be asked how you would sweep a codebase for it.

for a principal

Own the policy angle: whether a configurable insecure-TLS switch may exist at all, how certificate trust is distributed to services, and how you keep a temporary local workaround from being the thing that ships. Argue why the fix belongs in issuance, not in client code.

## The two checks, and which one verify_mode owns A Python TLS client performs two independent checks on the certificate the peer sends. **Chain validation** asks whether the certificate is signed, transitively, by a certificate authority this context has been told to trust, and whether it is currently valid. **Hostname matching** asks whether the name in the certificate covers the name that was dialled. `ssl.SSLContext.verify_mode` governs the first; `ssl.SSLContext.check_hostname` governs the second. Both matter, and turning off either one leaves a hole the other does not cover. `verify_mode` takes one of three module constants: * `ssl.CERT_REQUIRED` - the peer must present a certificate and it must validate. This is what a client wants, always. * `ssl.CERT_OPTIONAL` - in client mode this is effectively the same as `ssl.CERT_REQUIRED` (the anonymous cipher suites that would make the difference are disabled by default), so it is a confusing thing to write rather than a useful one. * `ssl.CERT_NONE` - the certificate is accepted without being checked against any trust anchor. ## Why unauthenticated encryption is not a partial win The common intuition is that `ssl.CERT_NONE` gives you *most* of TLS: the bytes are still encrypted, so surely a passive eavesdropper is still defeated. That is true and irrelevant, because the attacker who matters does not have to be passive. Anyone in the network path - a hostile Wi-Fi access point, a compromised load balancer, a router in a data centre you do not own - can accept your connection, present a certificate they generated a second ago, and open their own connection onward to the real server. You encrypt to them, they decrypt, read, modify and re-encrypt. Nothing in the transcript looks wrong to your process. Certificate verification is the *only* thing in TLS that distinguishes the real endpoint from that attacker, and `ssl.CERT_NONE` is precisely the switch that removes it. So a client running with `ssl.CERT_NONE` is not "weaker TLS"; against an active attacker it is a plaintext channel with extra CPU cost. ## The interlock CPython refuses to let you get there by accident: ```pycon >>> import ssl >>> ctx = ssl.create_default_context() >>> ctx.verify_mode = ssl.CERT_NONE ValueError: Cannot set verify_mode to CERT_NONE when check_hostname is enabled. ``` Hostname matching is meaningless without chain validation - an attacker who can mint their own certificate will happily put your hostname in it - so the `ssl` module will not let the two settings disagree. The practical effect is that disabling verification is always a deliberate two-line act (`check_hostname = False` first, then `verify_mode = ssl.CERT_NONE`), which makes it easy to spot in review and easy to grep for across a codebase. ## The other direction: what it means on a server `ssl.CERT_NONE` is *not* a red flag everywhere. A server context - one built with `ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)` - starts out with `check_hostname` false and `verify_mode` set to `ssl.CERT_NONE`, because an ordinary HTTPS server does not ask visitors for a client certificate. Raising a server to `ssl.CERT_REQUIRED` is how you turn on mutual TLS. So the same constant reads as "catastrophic" on a client and "normal" on a server, and the first question to ask about any `verify_mode` line is which side of the connection it configures. ## What people were actually trying to fix Almost every real `ssl.CERT_NONE` in a codebase started as a certificate error someone wanted to make go away, and each of those has a correct fix: * *An internal service signed by a private CA.* Add the CA with `ssl.SSLContext.load_verify_locations` instead of trusting everything. * *An expired or self-signed certificate.* Reissue it. A tolerated expiry today is a tolerated forgery tomorrow. * *A hostname mismatch* - the certificate covers one name and the code dials another, often an IP address. Reissue the certificate with the right subject alternative name, or dial the name it covers and let the address resolve. * *It works locally but not in the container.* The trust store differs between the two environments; inspect `ssl.get_default_verify_paths()` and the count from `ssl.SSLContext.get_ca_certs()` on each. ## Detecting it Because the interlock forces both assignments, a search for `check_hostname` set to false, or for `ssl.CERT_NONE` outside a server context, finds essentially every instance. Also treat any "verify", "insecure" or "skip TLS" boolean threaded through configuration as the same defect wearing a hat: a flag that can be flipped in production is a verification bypass that will eventually be flipped in production.

  • If the connection is still encrypted, what exactly can an attacker do that they could not do against a verifying client?
    They can be the endpoint. An attacker in the path accepts the connection with a certificate they generated, decrypts everything the client sends, alters it, and opens a second connection to the real server. Against a verifying client that fails at the handshake, because their certificate chains to nothing the client trusts. So verification is what turns confidentiality into confidentiality-with-a-known-counterparty.
  • Is ssl.CERT_NONE always a defect?
    No - it depends on which side you are configuring. On a server context it is the default and simply means the server does not request a client certificate; you raise it to `ssl.CERT_REQUIRED` only when you want mutual TLS. On a client context it is a verification bypass with no legitimate production use.
  • A colleague sets check_hostname to False but leaves verify_mode at ssl.CERT_REQUIRED. Is that safe?
    Weaker, and usually wrong. The chain still validates, so the certificate is issued by a trusted CA - but nothing checks that it was issued for the host being dialled. Any certificate from any CA in the trust store then satisfies the client, including one an attacker legitimately obtained for a domain they control.

It is a sealed envelope handed to whoever answers the door, with no attempt to check that the person at the door is who you came to see.

saying these in an interview costs you the question

  • Claims the connection is still safe because it is encrypted
  • Uses ssl.CERT_NONE to silence a certificate error in production
  • Thinks check_hostname alone protects against a forged certificate
  • Believes only a passive eavesdropper threatens TLS
  • Cannot say which side of the connection verify_mode is configuring
  • Treats ssl.CERT_OPTIONAL as a safe middle ground for a client

context