2015-05-10 01:25:07 -04:00
|
|
|
PEP: 493
|
|
|
|
Title: HTTPS verification recommendations for Python 2.7 redistributors
|
|
|
|
Version: $Revision$
|
|
|
|
Last-Modified: $Date$
|
2015-07-06 00:00:55 -04:00
|
|
|
Author: Nick Coghlan <ncoghlan@gmail.com>,
|
|
|
|
Robert Kuska <rkuska@redhat.com>,
|
|
|
|
Marc-André Lemburg <mal@lemburg.com>
|
2015-05-10 01:25:07 -04:00
|
|
|
Status: Draft
|
|
|
|
Type: Informational
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
Created: 10-May-2015
|
2015-07-06 00:00:55 -04:00
|
|
|
Post-History: 06-Jul-2015
|
2015-05-10 01:25:07 -04:00
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
|
2015-05-10 01:25:07 -04:00
|
|
|
Abstract
|
|
|
|
========
|
|
|
|
|
|
|
|
PEP 476 updated Python's default handling of HTTPS certificates to be
|
|
|
|
appropriate for communication over the public internet. The Python 2.7 long
|
|
|
|
term maintenance series was judged to be in scope for this change, with the
|
|
|
|
new behaviour introduced in the Python 2.7.9 maintenance release.
|
|
|
|
|
|
|
|
This PEP provides recommendations to downstream redistributors wishing to
|
|
|
|
provide a smoother migration experience when helping their users to manage
|
|
|
|
this change in Python's default behaviour.
|
|
|
|
|
2015-05-12 10:08:31 -04:00
|
|
|
*Note that this PEP is not currently accepted, so it is a *proposed*
|
|
|
|
recommendation, rather than an active one.*
|
2015-05-10 01:25:07 -04:00
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
|
2015-05-10 01:25:07 -04:00
|
|
|
Rationale
|
|
|
|
=========
|
|
|
|
|
|
|
|
PEP 476 changed Python's default behaviour to better match the needs and
|
|
|
|
expectations of developers operating over the public internet, a category
|
|
|
|
which appears to include most new Python developers. It is the position of
|
|
|
|
the authors of this PEP that this was a correct decision.
|
|
|
|
|
|
|
|
However, it is also the case that this change *does* cause problems for
|
|
|
|
infrastructure administrators operating private intranets that rely on
|
|
|
|
self-signed certificates, or otherwise encounter problems with the new default
|
|
|
|
certificate verification settings.
|
|
|
|
|
|
|
|
The long term answer for such environments is to update their internal
|
|
|
|
certificate management to at least match the standards set by the public
|
|
|
|
internet, but in the meantime, it is desirable to offer these administrators
|
|
|
|
a way to continue receiving maintenance updates to the Python 2.7 series,
|
|
|
|
without having to gate that on upgrades to their certificate management
|
|
|
|
infrastructure.
|
|
|
|
|
|
|
|
PEP 476 did attempt to address this question, by covering how to revert the
|
|
|
|
new settings process wide by monkeypatching the ``ssl`` module to restore the
|
|
|
|
old behaviour. Unfortunately, the ``sitecustomize.py`` based technique proposed
|
|
|
|
to allow system administrators to disable the feature by default in their
|
2015-05-12 10:08:31 -04:00
|
|
|
Standard Operating Environment definition has been determined to be
|
|
|
|
insufficient in at least some cases. The specific case of interest to the
|
|
|
|
authors of this PEP is the one where a Linux distributor aims to provide
|
|
|
|
their users with a
|
2015-05-12 08:14:44 -04:00
|
|
|
`smoother migration path <https://bugzilla.redhat.com/show_bug.cgi?id=1173041>`__
|
2015-05-12 10:08:31 -04:00
|
|
|
than the standard one provided by consuming upstream CPython 2.7 releases
|
|
|
|
directly, but other potential challenges have also been pointed out with
|
|
|
|
updating embedded Python runtimes and other user level installations of Python.
|
2015-05-10 01:25:07 -04:00
|
|
|
|
|
|
|
Rather than allowing a plethora of mutually incompatibile migration techniques
|
2015-05-12 08:14:44 -04:00
|
|
|
to bloom, this PEP proposes two alternative approaches that redistributors
|
|
|
|
may take when addressing these problems. Redistributors may choose to implement
|
|
|
|
one, both, or neither of these approaches based on their assessment of the
|
|
|
|
needs of their particular userbase.
|
|
|
|
|
|
|
|
These designs are being proposed as a recommendation for redistributors, rather
|
|
|
|
than as new upstream features, as they are needed purely to support legacy
|
|
|
|
environments migrating from older versions of Python 2.7. Neither approach
|
|
|
|
is being proposed as an upstream Python 2.7 feature, nor as a feature in any
|
|
|
|
version of Python 3 (whether published directly by the Python Software
|
|
|
|
Foundation or by a redistributor).
|
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
|
2015-07-05 20:45:09 -04:00
|
|
|
Requirements for capability detection
|
|
|
|
=====================================
|
|
|
|
|
|
|
|
As these recommendations are intended to cover backports to earlier Python
|
|
|
|
versions, the Python version number cannot be used as a reliable means for
|
|
|
|
detecting them. Instead, the recommendations are defined to allow the presence
|
|
|
|
or absence of the feature to be determined using the following technique::
|
|
|
|
|
|
|
|
python -c "import ssl; ssl._relevant_attribute"
|
|
|
|
|
|
|
|
This will fail with `AttributeError` (and hence a non-zero return code) if the
|
|
|
|
relevant capability is not available.
|
|
|
|
|
|
|
|
The marker attributes are prefixed with an underscore to indicate the
|
|
|
|
implementation dependent nature of these capabilities - not all Python
|
|
|
|
distributions will offer them, only those that are providing a multi-stage
|
|
|
|
migration process from the legacy HTTPS handling to the new default behaviour.
|
|
|
|
|
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
Recommendation for an environment variable based security downgrade
|
|
|
|
===================================================================
|
|
|
|
|
|
|
|
Some redistributors may wish to provide a per-application option to disable
|
|
|
|
certificate verification in selected applications that run on or embed CPython
|
|
|
|
without needing to modify the application itself.
|
|
|
|
|
|
|
|
In these cases, a configuration mechanism is needed that provides:
|
|
|
|
|
|
|
|
* an opt-out model that allows certificate verification to be selectively
|
|
|
|
turned off for particular applications after upgrading to a version of
|
|
|
|
Python that verifies certificates by default
|
|
|
|
* the ability for all users to configure this setting on a per-application
|
|
|
|
basis, rather than on a per-system, or per-Python-installation basis
|
|
|
|
|
|
|
|
This approach may be used for any redistributor provided version of Python 2.7,
|
|
|
|
including those that advertise themselves as providing Python 2.7.9 or later.
|
|
|
|
|
|
|
|
|
2015-07-05 20:45:09 -04:00
|
|
|
Required marker attribute
|
|
|
|
-------------------------
|
|
|
|
|
|
|
|
The required marker attribute on the ``ssl`` module when implementing this
|
|
|
|
recommendation is::
|
|
|
|
|
|
|
|
_https_verify_envvar = 'PYTHONHTTPSVERIFY'
|
|
|
|
|
|
|
|
This not only makes it straightforward to detect the presence (or absence) of
|
|
|
|
the capability, it also makes it possible to programmatically determine the
|
|
|
|
relevant environment variable name.
|
|
|
|
|
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
Recommended modifications to the Python standard library
|
|
|
|
--------------------------------------------------------
|
|
|
|
|
|
|
|
The recommended approach to providing a per-application configuration setting
|
|
|
|
for HTTPS certificate verification that doesn't require modifications to the
|
|
|
|
application itself is to:
|
|
|
|
|
|
|
|
* modify the ``ssl`` module to read the ``PYTHONHTTPSVERIFY`` environment
|
|
|
|
variable when the module is first imported into a Python process
|
|
|
|
* set the ``ssl._create_default_https_context`` function to be an alias for
|
|
|
|
``ssl._create_unverified_context`` if this environment variable is present
|
|
|
|
and set to ``'0'``
|
|
|
|
* otherwise, set the ``ssl._create_default_https_context`` function to be an
|
|
|
|
alias for ``ssl.create_default_context`` as usual
|
|
|
|
|
|
|
|
|
|
|
|
Example implementation
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
::
|
|
|
|
|
2015-07-05 20:45:09 -04:00
|
|
|
_https_verify_envvar = 'PYTHONHTTPSVERIFY'
|
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
def _get_https_context_factory():
|
2015-07-05 20:45:09 -04:00
|
|
|
config_setting = os.environ.get(_https_verify_envvar)
|
2015-05-19 09:02:00 -04:00
|
|
|
if config_setting == '0':
|
|
|
|
return _create_unverified_context
|
|
|
|
return create_default_context
|
|
|
|
|
|
|
|
_create_default_https_context = _get_https_context_factory()
|
|
|
|
|
|
|
|
|
|
|
|
Security Considerations
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
Relative to an unmodified version of CPython 2.7.9 or later, this approach
|
|
|
|
does introduce a new downgrade attack against the default security settings
|
|
|
|
that potentially allows a sufficiently determined attacker to revert Python
|
|
|
|
to the vulnerable configuration used in CPython 2.7.8 and earlier releases.
|
2015-07-05 20:45:09 -04:00
|
|
|
However, such an attack requires the ability to modify the execution
|
|
|
|
environment of a Python process prior to the import of the ``ssl`` module,
|
|
|
|
and any attacker with such access would already be able to modify the
|
|
|
|
behaviour of the underlying OpenSSL implementation.
|
2015-05-19 09:02:00 -04:00
|
|
|
|
|
|
|
|
2015-05-12 08:14:44 -04:00
|
|
|
Recommendation for backporting to earlier Python versions
|
|
|
|
=========================================================
|
|
|
|
|
|
|
|
Some redistributors, most notably Linux distributions, may choose to backport
|
|
|
|
the PEP 476 HTTPS verification changes to modified Python versions based on
|
|
|
|
earlier Python 2 maintenance releases. In these cases, a configuration
|
|
|
|
mechanism is needed that provides:
|
|
|
|
|
|
|
|
* an opt-in model that allows the decision to enable HTTPS certificate
|
|
|
|
verification to be made independently of the decision to upgrade to the
|
|
|
|
Python version where the feature was first backported
|
|
|
|
* the ability for system administrators to set the default behaviour of Python
|
|
|
|
applications and scripts run directly in the system Python installation
|
|
|
|
* the ability for the redistributor to consider changing the default behaviour
|
|
|
|
of *new* installations at some point in the future without impacting existing
|
|
|
|
installations that have been explicitly configured to skip verifying HTTPS
|
|
|
|
certificates by default
|
|
|
|
|
|
|
|
This approach should not be used for any Python installation that advertises
|
|
|
|
itself as providing Python 2.7.9 or later, as most Python users will have the
|
|
|
|
reasonable expectation that all such environments will validate HTTPS
|
|
|
|
certificates by default.
|
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
|
2015-07-05 20:45:09 -04:00
|
|
|
Required marker attribute
|
|
|
|
-------------------------
|
|
|
|
|
|
|
|
The required marker attribute on the ``ssl`` module when implementing this
|
|
|
|
recommendation is::
|
|
|
|
|
|
|
|
_cert_verification_config = '<path to configuration file>'
|
|
|
|
|
|
|
|
This not only makes it straightforward to detect the presence (or absence) of
|
|
|
|
the capability, it also makes it possible to programmatically determine the
|
|
|
|
relevant configuration file name.
|
|
|
|
|
|
|
|
|
2015-05-12 08:14:44 -04:00
|
|
|
Recommended modifications to the Python standard library
|
|
|
|
--------------------------------------------------------
|
|
|
|
|
|
|
|
The recommended approach to backporting the PEP 476 modifications to an earlier
|
|
|
|
point release is to implement the following changes relative to the default
|
|
|
|
PEP 476 behaviour implemented in Python 2.7.9+:
|
|
|
|
|
|
|
|
* modify the ``ssl`` module to read a system wide configuration file when the
|
|
|
|
module is first imported into a Python process
|
2015-05-19 09:02:00 -04:00
|
|
|
* define a platform default behaviour (either verifying or not verifying HTTPS
|
|
|
|
certificates) to be used if this configuration file is not present
|
2015-05-12 08:14:44 -04:00
|
|
|
* support selection between the following three modes of operation:
|
2015-05-10 01:25:07 -04:00
|
|
|
|
|
|
|
* ensure HTTPS certificate verification is enabled
|
|
|
|
* ensure HTTPS certificate verification is disabled
|
2015-05-12 08:14:44 -04:00
|
|
|
* delegate the decision to the redistributor providing this Python version
|
2015-05-10 01:25:07 -04:00
|
|
|
|
2015-05-12 08:14:44 -04:00
|
|
|
* set the ``ssl._create_default_https_context`` function to be an alias for
|
|
|
|
either ``ssl.create_default_context`` or ``ssl._create_unverified_context``
|
|
|
|
based on the given configuration setting.
|
2015-05-10 01:25:07 -04:00
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
|
2015-05-10 01:25:07 -04:00
|
|
|
Recommended file location
|
|
|
|
-------------------------
|
|
|
|
|
2015-05-12 08:14:44 -04:00
|
|
|
This approach is currently only defined for \*nix system Python installations.
|
|
|
|
|
|
|
|
The recommended configuration file name is
|
2015-05-19 09:02:00 -04:00
|
|
|
``/etc/python/cert-verification.cfg``.
|
2015-05-12 08:14:44 -04:00
|
|
|
|
|
|
|
The ``.cfg`` filename extension is recommended for consistency with the
|
|
|
|
``pyvenv.cfg`` used by the ``venv`` module in Python 3's standard library.
|
2015-05-10 01:25:07 -04:00
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
|
2015-05-10 01:25:07 -04:00
|
|
|
Recommended file format
|
|
|
|
-----------------------
|
|
|
|
|
2015-05-12 08:14:44 -04:00
|
|
|
The configuration file should use a ConfigParser ini-style format with a
|
2015-05-19 09:02:00 -04:00
|
|
|
single section named ``[https]`` containing one required setting ``verify``.
|
2015-05-12 08:14:44 -04:00
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
Permitted values for ``verify`` are:
|
2015-05-12 08:14:44 -04:00
|
|
|
|
|
|
|
* ``enable``: ensure HTTPS certificate verification is enabled by default
|
|
|
|
* ``disable``: ensure HTTPS certificate verification is disabled by default
|
|
|
|
* ``platform_default``: delegate the decision to the redistributor providing
|
|
|
|
this particular Python version
|
|
|
|
|
|
|
|
If the ``[https]`` section or the ``verify`` setting are missing, or if the
|
|
|
|
``verify`` setting is set to an unknown value, it should be treated as if the
|
|
|
|
configuration file is not present.
|
|
|
|
|
|
|
|
|
|
|
|
Example implementation
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
::
|
|
|
|
|
2015-07-05 20:45:09 -04:00
|
|
|
_cert_verification_config = '/etc/python/cert-verification.cfg'
|
|
|
|
|
2015-05-12 08:14:44 -04:00
|
|
|
def _get_https_context_factory():
|
2015-05-19 09:02:00 -04:00
|
|
|
# Check for a system-wide override of the default behaviour
|
2015-05-12 08:14:44 -04:00
|
|
|
context_factories = {
|
|
|
|
'enable': create_default_context,
|
|
|
|
'disable': _create_unverified_context,
|
|
|
|
'platform_default': _create_unverified_context, # For now :)
|
|
|
|
}
|
2015-05-19 09:02:00 -04:00
|
|
|
import ConfigParser
|
|
|
|
config = ConfigParser.RawConfigParser()
|
2015-07-05 20:45:09 -04:00
|
|
|
config.read(_cert_verification_config)
|
2015-05-12 08:14:44 -04:00
|
|
|
try:
|
|
|
|
verify_mode = config.get('https', 'verify')
|
|
|
|
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
|
2015-05-19 09:30:08 -04:00
|
|
|
verify_mode = 'platform_default'
|
2015-05-19 09:02:00 -04:00
|
|
|
default_factory = context_factories.get('platform_default')
|
|
|
|
return context_factories.get(verify_mode, default_factory)
|
2015-05-12 08:14:44 -04:00
|
|
|
|
|
|
|
_create_default_https_context = _get_https_context_factory()
|
|
|
|
|
2015-05-10 01:25:07 -04:00
|
|
|
|
|
|
|
Security Considerations
|
2015-05-12 08:14:44 -04:00
|
|
|
-----------------------
|
2015-05-10 01:25:07 -04:00
|
|
|
|
2015-05-12 08:14:44 -04:00
|
|
|
The specific recommendations for the backporting case are designed to work for
|
|
|
|
privileged, security sensitive processes, even those being run in the following
|
|
|
|
locked down configuration:
|
2015-05-10 01:25:07 -04:00
|
|
|
|
|
|
|
* run from a locked down administrator controlled directory rather than a normal
|
|
|
|
user directory (preventing ``sys.path[0]`` based privilege escalation attacks)
|
|
|
|
* run using the ``-E`` switch (preventing ``PYTHON*`` environment variable based
|
|
|
|
privilege escalation attacks)
|
|
|
|
* run using the ``-s`` switch (preventing user site directory based privilege
|
|
|
|
escalation attacks)
|
|
|
|
* run using the ``-S`` switch (preventing ``sitecustomize`` based privilege
|
|
|
|
escalation attacks)
|
|
|
|
|
|
|
|
The intent is that the *only* reason HTTPS verification should be getting
|
2015-05-12 08:14:44 -04:00
|
|
|
turned off system wide when using this approach is because:
|
2015-05-10 01:25:07 -04:00
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
* an end user is running a redistributor provided version of CPython rather
|
2015-05-10 01:25:07 -04:00
|
|
|
than running upstream CPython directly
|
|
|
|
* that redistributor has decided to provide a smoother migration path to
|
|
|
|
verifying HTTPS certificates by default than that being provided by the
|
|
|
|
upstream project
|
|
|
|
* either the redistributor or the local infrastructure administrator has
|
|
|
|
determined that it is appropriate to override the default upstream behaviour
|
|
|
|
(at least for the time being)
|
|
|
|
|
|
|
|
Using an administrator controlled configuration file rather than an environment
|
2015-07-05 20:45:09 -04:00
|
|
|
variable has the essential feature of providing a smoother migration path, even
|
2015-05-19 09:02:00 -04:00
|
|
|
for applications being run with the ``-E`` switch.
|
2015-05-10 01:25:07 -04:00
|
|
|
|
2015-05-12 08:14:44 -04:00
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
Combining the recommendations
|
|
|
|
=============================
|
2015-05-12 08:14:44 -04:00
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
If a redistributor chooses to implement both recommendations, then the
|
|
|
|
environment variable should take precedence over the system-wide configuration
|
|
|
|
setting. This allows the setting to be changed for a given user, virtual
|
|
|
|
environment or application, regardless of the system-wide default behaviour.
|
2015-05-12 08:14:44 -04:00
|
|
|
|
2015-05-19 09:17:09 -04:00
|
|
|
In this case, if the ``PYTHONHTTPSVERIFY`` environment variable is defined, and
|
|
|
|
set to anything *other* than ``'0'``, then HTTPS certificate verification
|
|
|
|
should be enabled.
|
2015-05-12 08:14:44 -04:00
|
|
|
|
|
|
|
Example implementation
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
::
|
|
|
|
|
2015-07-05 20:45:09 -04:00
|
|
|
_https_verify_envvar = 'PYTHONHTTPSVERIFY'
|
|
|
|
_cert_verification_config = '/etc/python/cert-verification.cfg'
|
|
|
|
|
2015-05-12 08:14:44 -04:00
|
|
|
def _get_https_context_factory():
|
2015-05-19 09:02:00 -04:00
|
|
|
# Check for am environmental override of the default behaviour
|
2015-07-05 20:45:09 -04:00
|
|
|
config_setting = os.environ.get(_https_verify_envvar)
|
2015-05-19 09:02:00 -04:00
|
|
|
if config_setting is not None:
|
|
|
|
if config_setting == '0':
|
|
|
|
return _create_unverified_context
|
|
|
|
return create_default_context
|
2015-05-12 08:14:44 -04:00
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
# Check for a system-wide override of the default behaviour
|
|
|
|
context_factories = {
|
|
|
|
'enable': create_default_context,
|
|
|
|
'disable': _create_unverified_context,
|
|
|
|
'platform_default': _create_unverified_context, # For now :)
|
|
|
|
}
|
|
|
|
import ConfigParser
|
|
|
|
config = ConfigParser.RawConfigParser()
|
2015-07-05 20:45:09 -04:00
|
|
|
config.read(_cert_verification_config)
|
2015-05-19 09:02:00 -04:00
|
|
|
try:
|
|
|
|
verify_mode = config.get('https', 'verify')
|
|
|
|
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
|
2015-05-19 09:30:08 -04:00
|
|
|
verify_mode = 'platform_default'
|
2015-05-19 09:02:00 -04:00
|
|
|
default_factory = context_factories.get('platform_default')
|
|
|
|
return context_factories.get(verify_mode, default_factory)
|
2015-05-12 08:14:44 -04:00
|
|
|
|
2015-05-19 09:02:00 -04:00
|
|
|
_create_default_https_context = _get_https_context_factory()
|
2015-05-12 08:14:44 -04:00
|
|
|
|
|
|
|
|
2015-05-10 01:25:07 -04:00
|
|
|
Copyright
|
|
|
|
=========
|
|
|
|
|
|
|
|
This document has been placed into the public domain.
|
|
|
|
|
|
|
|
|
|
|
|
..
|
|
|
|
Local Variables:
|
|
|
|
mode: indented-text
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
sentence-end-double-space: t
|
|
|
|
fill-column: 70
|
|
|
|
coding: utf-8
|