# \`ssl\`: changing the default \`SSLContext.verify\_flags\`?

**URL:** <https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230>\
**Category:** Core Development\
**Tags:** security\
**Created:** [July 25, 2023, 4:10am UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230 "2023-07-25T04:10:06Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![woodruffw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/woodruffw/32/37228_2.png) [@woodruffw](https://discuss.python.org/u/woodruffw)\
**Post date:** [July 25, 2023, 4:10am UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/1 "2023-07-25T04:10:06Z")

</div>

At the moment (CPython 3.11), the default `SSLContext.verify_flags` is set to just `VERIFY_X509_TRUSTED_FIRST`:

```python
>>> import ssl
>>> ssl.create_default_context().verify_flags  
<VerifyFlags.VERIFY_X509_TRUSTED_FIRST: 32768>

```

I think there are two additional flags that (likely) make sense to include as defaults:

- `VERIFY_X509_STRICT`: this performs more strict X.509 certificate validation, which is what users _probably_ generally expect (insofar as the average user is probably talking to TLS server that’s supposed to be conforming with the CA/B Basic Requirements anyways).
- `VERIFY_X509_PARTIAL_CHAIN`: despite the confusing name, this flag makes OpenSSL’s path building behave more consistently (and more like other path building implementations): it terminates the chain building process as soon as a trust anchor is encountered, rather than continuing to search the trust store for a _self-signed_ trust anchor.

Together, these additional flags would make the `ssl` module’s default path building/certificate validation rules more consistent with how Go, GnuTLS[1], etc. behave. At the same time, making them stricter _shouldn’t_ affect the vast majority of users, since the Web PKI already enforces (as a matter of policy) the things OpenSSL considers “strict.”

I’m raising this to get opinions from others, particularly with respect to compatibility/consequences that I might be oblivious to.

CC @sethmlarson, who may have opinions about this as the SecDevInRes 🙂

[1]: This is not otherwise an endorsement of these implementations; just that they perform my default the standard behavior that OpenSSL chooses to tuck behind `VERIFY_X509_PARTIAL_CHAIN`

---

<div class="post-metadata">

**Author:** ![pitrou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pitrou/32/28_2.png) [@pitrou](https://discuss.python.org/u/pitrou)\
**Post date:** [July 25, 2023, 4:31pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/2 "2023-07-25T16:31:14Z")

</div>

cc @tiran @gpshead

---

<div class="post-metadata">

**Author:** ![gpshead](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/gpshead/32/54_2.png) [@gpshead](https://discuss.python.org/u/gpshead)\
**Post date:** [July 25, 2023, 6:44pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/3 "2023-07-25T18:44:34Z")

</div>

Changing them in 3.13 is possible if the new defaults make more sense. I’m interested to hear what might be considered the TLS certificate verification strictness best practices here from someone deep in the TLS world such as @davidben.

My own _presumption_ is that our certificate verification default behaviors would ideally match what major browsers like Chromium and Firefox do?

---

<div class="post-metadata">

**Author:** ![alex\_Gaynor](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/alex_gaynor/32/17_2.png) [@alex\_Gaynor](https://discuss.python.org/u/alex_Gaynor)\
**Post date:** [July 25, 2023, 7:02pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/4 "2023-07-25T19:02:00Z")

</div>

`VERIFY_X509_PARTIAL_CHAIN` makes sense to me as a reasonable default.

For `VERIFY_X509_STRICT` I’d want to understand a bit more about what specifically it does.

---

<div class="post-metadata">

**Author:** ![woodruffw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/woodruffw/32/37228_2.png) [@woodruffw](https://discuss.python.org/u/woodruffw)\
**Post date:** [July 26, 2023, 4:45am UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/5 "2023-07-26T04:45:44Z")

</div>

> [@alex\_Gaynor](#):
>
> For `VERIFY_X509_STRICT` I’d want to understand a bit more about what specifically it does.

This isn’t exhaustive, but from a quick look: without `X509_V_FLAG_X509_STRICT` (the equivalent on the OpenSSL C API side), OpenSSL fails to enforce the `basicConstraints`, `keyUsage`, and issuer/subject/SAN rules (among others) required by RFC 5280.

Reference:

> <https://github.com/openssl/openssl/blob/bc5d9cc8711e86d5c25b81c58dfae531536e61fc/crypto/x509/x509_vfy.c#L577-L643>

---

<div class="post-metadata">

**Author:** ![sethmlarson](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/sethmlarson/32/1644_2.png) [@sethmlarson](https://discuss.python.org/u/sethmlarson)\
**Post date:** [July 26, 2023, 5:57pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/6 "2023-07-26T17:57:47Z")

</div>

`VERIFY_X509_PARTIAL_CHAIN` makes sense to me, and if the user-perceived difference when that default is changed is potentially shorter trust chains that feels like an acceptable difference to have without a deprecation period? (Would that only be accessible through the private certificate chain APIs on `SSLObject`?)

I am worried about unintended breakages for `VERIFY_X509_STRICT`, it’s likely totally fine for public-facing websites for the reasons you mentioned but enterprise configurations are more varied having experience with changing default certificate verification behaviors in the past. Is there a way we can detect when a given certificate would have caused problems and emit a `DeprecationWarning` or something like this for folks so we can get an early signal to what magnitude of breakage we can expect from changing the default there?

---

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [July 26, 2023, 6:08pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/7 "2023-07-26T18:08:45Z")

</div>

Changing default is always scary. What would be the option for users affected by stricter default? Is there a way to configure the Python crypto policy?

Fedora has a nice system-wide `update-crypto-policies` command which can be used by the admin for that:

- [“Legacy” cryptography in Fedora 36 and Red Hat Enterprise Linux 9](https://www.redhat.com/en/blog/legacy-cryptography-fedora-36-and-red-hat-enterprise-linux-9)
- [Changes/StrongCryptoSettings2 - Fedora Project Wiki](https://fedoraproject.org/wiki/Changes/StrongCryptoSettings2)

When RHEL 7 was modified to enable HTTPS certification validation, a new configuration was added to give the choice to opt-in for the old unsafe default (don’t check certificates):

- `/etc/python/cert-verification.cfg` configuration file (RHEL specific)
- `PYTHONHTTPSVERIFY` env var (RHEL specific)
- [Certificate verification in Python standard library HTTP clients - Red Hat Customer Portal](https://access.redhat.com/articles/2039753)

I vaguely recall that when we fixed the stdlib to prevent sending invalid HTTP requests, we added an opt-in API for people who want to send invalid HTTP requests on purpose. But I failed to find a reference to that 🙂

cc @encukou

---

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [July 26, 2023, 6:10pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/8 "2023-07-26T18:10:01Z")

</div>

See also [Enable TLS certificate validation by default for SMTP/IMAP/FTP/POP/NNTP protocols · Issue #91826 · python/cpython · GitHub](https://github.com/python/cpython/issues/91826) : Python stdlib still doesn’t validate certificates **by default** for SMTP,IMAP, FTP, POP and NNTP protocols ☹ Again, changing that would break a lot of projects, but we should do it at some point.

---

<div class="post-metadata">

**Author:** ![woodruffw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/woodruffw/32/37228_2.png) [@woodruffw](https://discuss.python.org/u/woodruffw)\
**Post date:** [July 26, 2023, 11:40pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/9 "2023-07-26T23:40:20Z")

</div>

> [@sethmlarson](#):
>
> Is there a way we can detect when a given certificate would have caused problems and emit a `DeprecationWarning` or something like this for folks so we can get an early signal to what magnitude of breakage we can expect from changing the default there?

In order to do this, I think we’d effectively need to run every X.509 path validation twice: once with the flag, and once without. That’s probably _possible_ since CPython controls all of the inputs here, but I have no idea whether it’s advisable or not 🙂 – it’d be a bit of a performance hit and we’d need to be careful about ownership/not misusing OpenSSL’s APIs across two verification contexts.

> [@vstinner](#):
>
> What would be the option for users affected by stricter default? Is there a way to configure the Python crypto policy?

Those users could explicitly configure a weaker set of `SSLContext.verify_flags`, e.g.:

```python
>>> ctx.verify_flags 
<VerifyFlags.VERIFY_X509_TRUSTED_FIRST|VERIFY_X509_PARTIAL_CHAIN: 557056>
>>> ctx.verify_flags -= ssl.VERIFY_X509_PARTIAL_CHAIN
>>> ctx.verify_flags 
<VerifyFlags.VERIFY_X509_TRUSTED_FIRST: 32768>

```

If by “crypto policy” you mean something more global than that (e.g. an environment variable that sets the `verify_flags`), then I don’t believe that _currently_ exists (but I might be wrong!)

IMO adding that kind of global switch here might cause more problems than it solves: even users who need weaker X.509 verification settings _probably_ don’t need them on a per-interpreter basis, but probably only need them for a specific handful of uses of `ssl`. Giving them a big flag that makes the error go away might result in a lot of people opting into a worse overall security posture for just a single use of `ssl` that actually needs it 🙂

---

<div class="post-metadata">

**Author:** ![Glenn](https://avatars.discourse-cdn.com/v4/letter/g/4bbf92/32.png) [@Glenn](https://discuss.python.org/u/Glenn)\
**Post date:** [July 27, 2023, 1:26am UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/10 "2023-07-27T01:26:18Z")

</div>

So if you tried the more secure way first and it worked, you wouldn’t  
have to do it twice. But if you got a validation error, you could try  
it again with the current default settings, and report the warning?

---

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [July 27, 2023, 9:14am UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/11 "2023-07-27T09:14:59Z")

</div>

> [@woodruffw](#):
>
> If by “crypto policy” you mean something more global than that (e.g. an environment variable that sets the `verify_flags`), then I don’t believe that _currently_ exists (but I might be wrong!)

I would suggest adding such global configuration, right.

---

<div class="post-metadata">

**Author:** ![pitrou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pitrou/32/28_2.png) [@pitrou](https://discuss.python.org/u/pitrou)\
**Post date:** [July 27, 2023, 10:02am UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/12 "2023-07-27T10:02:04Z")

</div>

The doc for [create\_default\_context](https://docs.python.org/3/library/ssl.html#ssl.create_default_context) explicitly notes that:

> The protocol, options, cipher and other settings may change to more restrictive values anytime without prior deprecation. The values represent a fair balance between compatibility and security.

In other words, `create_default_context` was **designed** to represent an evolutive default choice adequate for common TLS usage. It is perfectly fine to change those defaults to stricter values in a new CPython release, and no deprecation period is needed.

And as the doc goes on to say:

> If your application needs specific settings, you should create a [`SSLContext`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext) and apply the settings yourself.

---

<div class="post-metadata">

**Author:** ![sethmlarson](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/sethmlarson/32/1644_2.png) [@sethmlarson](https://discuss.python.org/u/sethmlarson)\
**Post date:** [July 27, 2023, 3:01pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/13 "2023-07-27T15:01:17Z")

</div>

> [@woodruffw](#):
>
> > [@sethmlarson](#):
> >
> > Is there a way we can detect when a given certificate would have caused problems and emit a `DeprecationWarning` or something like this for folks so we can get an early signal to what magnitude of breakage we can expect from changing the default there?
> 
> In order to do this, I think we’d effectively need to run every X.509 path validation twice: once with the flag, and once without. That’s probably _possible_ since CPython controls all of the inputs here, but I have no idea whether it’s advisable or not 🙂 – it’d be a bit of a performance hit and we’d need to be careful about ownership/not misusing OpenSSL’s APIs across two verification contexts.

So this is semi-complicated, but it is one potential way we could offer a deprecation period that folks could opt-in to non-strict verification or be alerted to their non-standard certificate situation. This explanation leaves out the difficulty in implementing the behavior of OpenSSL’s strict verification, I looked briefly but didn’t do a deep-dive into feasibility there. Folks that know more should weigh in on this aspect.

During the deprecation period:

- Track whether `SSLContext.verify_flags` has been set after being put into user control (ie, after `ssl.create_default_context()`)
- On the handshake do a normal path validation without `VERIFY_X509_STRICT` enabled.
- After building a chain, if `SSLContext.verify_flags` hasn’t been modified since creation:
  - Inspect the certificates in the chain for the conditions where OpenSSL would have failed on if `VERIFY_X509_STRICT` were enabled.
  - Emit a `DeprecationWarning` if any of those cases are found with links to documentation.
  - Documentation shows way to suppress the deprecation (ie `SSLContext.verify_flags &= ~ssl.VERIFY_X509_STRICT` which would unset the “unmodified verify\_flags” flag on `SSLContext`)

Then after the deprecation period we can add `VERIFY_X509_STRICT` as a default.

---

<div class="post-metadata">

**Author:** ![woodruffw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/woodruffw/32/37228_2.png) [@woodruffw](https://discuss.python.org/u/woodruffw)\
**Post date:** [July 27, 2023, 3:20pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/14 "2023-07-27T15:20:15Z")

</div>

> [@pitrou](#):
>
> In other words, `create_default_context` was **designed** to represent an evolutive default choice adequate for common TLS usage. It is perfectly fine to change those defaults to stricter values in a new CPython release, and no deprecation period is needed.

I totally missed this!

Based on the change notes below that, it looks like quite a few other “breaking” changes have been made to `create_default_context` under the same policy (e.g., dropping 3DES).

Given that, IMO it makes sense to add `VERIFY_X509_STRICT` with no deprecation period: it’s the same sort of explicitly allowed “breaking” change.

---

<div class="post-metadata">

**Author:** ![gpshead](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/gpshead/32/54_2.png) [@gpshead](https://discuss.python.org/u/gpshead)\
**Post date:** [July 27, 2023, 7:08pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/15 "2023-07-27T19:08:38Z")

</div>

yep, feel free to file an issue and make a PR.

---

<div class="post-metadata">

**Author:** ![woodruffw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/woodruffw/32/37228_2.png) [@woodruffw](https://discuss.python.org/u/woodruffw)\
**Post date:** [July 27, 2023, 9:11pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/16 "2023-07-27T21:11:17Z")

</div>

Thanks all!

I’ve opened a GitHub issue here: [`ssl.create_default_context()`: add `VERIFY_X509_STRICT` and `VERIFY_X509_PARTIAL_CHAIN` to the default `verify_flags` · Issue #107361 · python/cpython · GitHub](https://github.com/python/cpython/issues/107361)

I’ll let that sit for a bit, and then open a corresponding PR.

---

<div class="post-metadata">

**Author:** ![woodruffw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/woodruffw/32/37228_2.png) [@woodruffw](https://discuss.python.org/u/woodruffw)\
**Post date:** [November 28, 2023, 8:46pm UTC](https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/17 "2023-11-28T20:46:13Z")

</div>

Just to seal things off here: I’ve opened [gh-107361: strengthen default SSL context flags by woodruffw · Pull Request #112389 · python/cpython · GitHub](https://github.com/python/cpython/pull/112389) with the proposed changes. It includes both flags discussed above, and updates the test suite + X.509 assets.
