Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSL certificate verification. #791

Merged
merged 59 commits into from
Feb 19, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
b9ea089
Changing the default index URL to use HTTPS.
Feb 4, 2013
246e974
Added certificate validation against root CA file.
Feb 4, 2013
db79f83
Added CA file to MANIFEST.in
Feb 4, 2013
39d84ab
Adding PEM file to package data.
Feb 4, 2013
d54c695
Adding code to match_hostname.
Feb 4, 2013
7776cfc
added install_requires
Feb 4, 2013
b97969f
'package_dir' not needed
qwcode Feb 6, 2013
6bd3bd4
won't be installing the ssl backport as a dependency
qwcode Feb 6, 2013
11ebe01
warn before installing if no ssl module
qwcode Feb 6, 2013
c612db0
match_hostname from py32
qwcode Feb 6, 2013
73c4614
backwardcompat logic for ssl and match_hostname
qwcode Feb 6, 2013
4bb5ac6
use standard opener when no ssl
qwcode Feb 6, 2013
7e20fd8
move cert_path to locations module
qwcode Feb 6, 2013
b2e0b6d
log relevant message if URLError.reason is SSLError or CertificateError
qwcode Feb 6, 2013
a4a9197
py25 import fixes
qwcode Feb 6, 2013
d50a3b7
license for CA Root Certificates
qwcode Feb 7, 2013
7fac01a
misc test fixes
qwcode Feb 7, 2013
8496406
--cert-path and --no-ssl options
qwcode Feb 7, 2013
226768d
allow py25 tests to work without ssl
qwcode Feb 7, 2013
193562b
more py25 test fixes
qwcode Feb 7, 2013
d8fe463
tun py25 test logic and add assert_raises_regexp
qwcode Feb 8, 2013
456ea80
fix param name in exception message
qwcode Feb 8, 2013
fe17bb4
OpenerDirector for ssl should not contain the default http handler
qwcode Feb 8, 2013
d77380d
ssl cert tests
qwcode Feb 8, 2013
609cfe9
remove duplicate backwardcompat imports and excess logic
qwcode Feb 8, 2013
6dc3212
pypy ssl test fix
qwcode Feb 9, 2013
bb7ba1a
shorter metavar for --cert-path
qwcode Feb 9, 2013
e338436
update authors and changelog
qwcode Feb 9, 2013
857f64e
latest mozilla ca certs aquired securely and generated to pem form
qwcode Feb 9, 2013
49563cf
ssl cert docs updates
qwcode Feb 9, 2013
f92052f
py25 socket patch to work with ssl backport
qwcode Feb 9, 2013
76b5ebc
use the more common phrase 'CA bundle'
qwcode Feb 10, 2013
83d8b37
only show --allow-no-ssl when no ssl
qwcode Feb 10, 2013
912784f
ssl docs fix
qwcode Feb 10, 2013
9cda4d6
--allow-no-ssl test fix
qwcode Feb 10, 2013
8fc02eb
py25 install tests with ssl backport
qwcode Feb 11, 2013
1cf1a7e
refactor pip.backwardcompat from module to package
qwcode Feb 11, 2013
a6a0ee3
update installation docs related to ssl
qwcode Feb 11, 2013
84f8134
remove HTTPHandler explictly
qwcode Feb 11, 2013
f13f879
unable to get ssl backport to install on travis
qwcode Feb 12, 2013
9b57f89
use local packages, not 'mock', which has invalid ssl download link
qwcode Feb 16, 2013
7c8470a
more local test packages
qwcode Feb 16, 2013
d0c3138
merge with develop
qwcode Feb 16, 2013
809a996
clear up connection compatibility logic
qwcode Feb 16, 2013
22bf924
merge with develop
qwcode Feb 16, 2013
b2a17e5
remove old cert from pem file
qwcode Feb 16, 2013
1857ece
fix import syntax
qwcode Feb 16, 2013
c4753b2
add missing sys import
qwcode Feb 18, 2013
26f9c14
move CA license to our license file
qwcode Feb 18, 2013
f72ddaa
our next virtualenv release will be 1.9
qwcode Feb 18, 2013
a2ba2dc
remove yum/apt-get instructions, since they will likely install non-s…
qwcode Feb 18, 2013
e3f1ce8
proper list indents
qwcode Feb 18, 2013
559d77a
from --cert-path to --cert
qwcode Feb 18, 2013
4a4a141
from --allow-no-ssl to --insecure
qwcode Feb 18, 2013
039e1fc
have 'pip search' use https index url
qwcode Feb 18, 2013
2cbc7fa
improve ssl exception text and docs
qwcode Feb 18, 2013
889e1a0
custom NoSSLError exception instead of util function
qwcode Feb 18, 2013
db9c92a
remove extra indents in docs
qwcode Feb 19, 2013
d6bb9a5
add TODO about options passing
qwcode Feb 19, 2013
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Ian Bicking
Igor Sobreira
Ionel Maries Cristian
Jakub Vysoky
James Cleveland
Jannis Leidel
Jay Graves
John-Scott Atlakson
Expand Down
3 changes: 3 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Changelog
develop (unreleased)
--------------------

* SSL Cert Verification; Make https the default for PyPI access.
Thanks James Cleveland, Giovanni Bajo and many others (Pull #789).

* Added "pip list" for listing installed packages and the latest version
available. Thanks Rafael Caricio, Miguel Araujo, Dmitry Gladkov (Pull #752)

Expand Down
19 changes: 19 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,22 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


License for Bundle of CA Root Certificates (pip/cacert.pem)
===========================================================

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ include AUTHORS.txt
include LICENSE.txt
include CHANGES.txt
include PROJECT.txt
include pip/cacert.pem
recursive-include docs *.txt
recursive-include docs *.html
recursive-exclude docs/_build *.txt
Expand Down
2 changes: 1 addition & 1 deletion docs/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A tool for installing and managing Python packages.
`Mailing list <http://groups.google.com/group/python-virtualenv>`_ ``|``
`Issues <https://github.com/pypa/pip/issues>`_ ``|``
`Github <https://github.com/pypa/pip>`_ ``|``
`PyPI <http://pypi.python.org/pypi/pip/>`_ ``|``
`PyPI <https://pypi.python.org/pypi/pip/>`_ ``|``
irc:#pip


Expand Down
48 changes: 34 additions & 14 deletions docs/installing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@
Installation
============

Python Support
--------------
.. warning::

Prior to version 1.3, pip did not use SSL for downloading packages from PyPI, and thus left
users more vulnerable to security threats. We advise installing at least version 1.3.
If you're using `virtualenv <http://www.virtualenv.org>`_ to install pip, we advise installing
at least version 1.9, which contains pip version 1.3.


Python & OS Support
-------------------

pip works with CPython versions 2.5, 2.6, 2.7, 3.1, 3.2, 3.3 and also pypy.

pip works on Unix/Linux, OS X, and Windows.


Using virtualenv
----------------
Expand All @@ -33,40 +43,50 @@ Installing Globally
pip can be installed globally in order to manage global packages.
Often this requires the installation to be performed as root.

Prerequisites
+++++++++++++
.. warning::

We advise against using easy_install to install pip, because easy_install
does not download from PyPI over SSL, so the installation might be insecure.
Since pip can then be used to install packages (which execute code on
your computer), it is better to go through a trusted path.


A global install of pip requires either `setuptools <http://pypi.python.org/pypi/setuptools>`_
or `distribute <http://pypi.python.org/pypi/distribute>`_ to be installed globally as well.
Requirements
++++++++++++

In many cases, these can be installed using your OS package manager.
pip requires either `setuptools <https://pypi.python.org/pypi/setuptools>`_
or `distribute <https://pypi.python.org/pypi/distribute>`_.

See the `Distribute Install Instructions <http://pypi.python.org/pypi/distribute/>`_ or the
`Setuptools Install Instructions <http://pypi.python.org/pypi/setuptools#installation-instructions>`_
See the `Distribute Install Instructions <https://pypi.python.org/pypi/distribute/>`_ or the
`Setuptools Install Instructions <https://pypi.python.org/pypi/setuptools#installation-instructions>`_

If installing pip using a linux package manager, these requirements will be installed for you.

.. warning::

If you are using Python 3.X you **must** use distribute; setuptools doesn't
support Python 3.X.


Using get-pip
+++++++++++++

Download `get-pip.py <https://raw.github.com/pypa/pip/master/contrib/get-pip.py>`_
and execute it using Python. This will only install pip, not the prerequisites.
After installing the requirements:

::

$ curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py
$ [sudo] python get-pip.py


From source
+++++++++++
Installing from source
++++++++++++++++++++++

After installing the requirements:

::

$ curl -O http://pypi.python.org/packages/source/p/pip/pip-X.X.tar.gz
$ curl -O https://pypi.python.org/packages/source/p/pip/pip-X.X.tar.gz
$ tar xvfz pip-X.X.tar.gz
$ cd pip-X.X
$ [sudo] python setup.py install
Expand Down
60 changes: 58 additions & 2 deletions docs/logic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,73 @@ pip offers a set of :ref:`Package Index Options <Package Index Options>` for mod
See the :ref:`pip install Examples<pip install Examples>`.


.. _`SSL Certificate Verification`:

SSL Certificate Verification
============================

Starting with v1.3, pip provides SSL certificate verification over https, for the purpose
of providing secure, certified downloads from PyPI.

This is supported by default in all Python versions pip supports, except Python 2.5.

Python 2.5 users can :ref:`install an SSL backport <SSL Backport>`, which provides ssl support for older pythons.
Pip does not try to install this automatically because it requires a compiler, which not all systems will have.

Although not recommended, Python 2.5 users who are unable to install ssl, can use the global option,
``--insecure``, to allow access to PyPI w/o attempting SSL certificate verification. This option will only be visible
when ssl is not importable. This is *not* a general option.


.. _`SSL Backport`:

Installing the SSL Backport
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. warning::

We advise against using ``pip`` itself to install the ssl backport, because it won't be secure
until *after* installing ssl. Likewise, ``easy_install`` is not advised, because it
does not currently support ssl.


1. Download the ssl archive:

* Using a Browser:

1. Go to `this url <https://pypi.python.org/pypi/ssl/1.15>`_.
2. Confirm the identity of the site is valid.
Most browsers provide this information to the left of the URL bar in the form of padlock icon that you can click on to confirm the site is verified.
3. Scroll down, and click to download ``ssl-1.15.tar.gz``.

* Using curl, which supports ssl certificate verification:
::

$ curl -O https://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz

2. Confirm the md5sum:
::
$ md5sum ssl-1.15.tar.gz
81ea8a1175e437b4c769ae65b3290e0c ssl-1.15.tar.gz

3. Unpack the archive, and change into the ``ssl-1.15`` directory.
4. Run: ``python setup.py install``.


Hash Verification
=================

PyPI provides an md5 hash of a package by having the link to the
package include an #md5=<hash>.
PyPI provides md5 hashes in the hash fragment of package download urls.

pip supports checking this, as well as any of the
guaranteed hashlib algorithms (sha1, sha224, sha384, sha256, sha512, md5).

The hash fragment is case sensitive (i.e. sha1 not SHA1).

This check is only intended to provide basic download corruption protection.
It is not intended to provide security against tampering. For that,
see :ref:`SSL Certificate Verification`


Download Cache
==============
Expand Down
21 changes: 21 additions & 0 deletions pip/backwardcompat.py → pip/backwardcompat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,24 @@ def home_lib(home):
else:
lib = os.path.join('lib', 'python')
return os.path.join(home, lib)


## py25 has no builtin ssl module
## only >=py32 has ssl.match_hostname and ssl.CertificateError
try:
import ssl
try:
from ssl import match_hostname, CertificateError
except ImportError:
from pip.backwardcompat.ssl_match_hostname import match_hostname, CertificateError
except ImportError:
ssl = None


# patch for py25 socket to work with http://pypi.python.org/pypi/ssl/
import socket
if not hasattr(socket, 'create_connection'): # for Python 2.5
# monkey-patch socket module
from pip.backwardcompat.socket_create_connection import create_connection
socket.create_connection = create_connection

44 changes: 44 additions & 0 deletions pip/backwardcompat/socket_create_connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
patch for py25 socket to work with http://pypi.python.org/pypi/ssl/
copy-paste from py2.6 stdlib socket.py
https://gist.github.com/zed/1347055
"""
import socket
import sys

_GLOBAL_DEFAULT_TIMEOUT = getattr(socket, '_GLOBAL_DEFAULT_TIMEOUT', object())
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
source_address=None):
"""Connect to *address* and return the socket object.

Convenience function. Connect to *address* (a 2-tuple ``(host,
port)``) and return the socket object. Passing the optional
*timeout* parameter will set the timeout on the socket instance
before attempting to connect. If no *timeout* is supplied, the
global default timeout setting returned by :func:`getdefaulttimeout`
is used.
"""

host, port = address
err = None
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
sock = None
try:
sock = socket.socket(af, socktype, proto)
if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(timeout)
if source_address:
sock.bind(source_address)
sock.connect(sa)
return sock

except socket.error:
err = sys.exc_info()[1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sys is undefined here.

if sock is not None:
sock.close()

if err is not None:
raise err
else:
raise socket.error("getaddrinfo returns an empty list")
60 changes: 60 additions & 0 deletions pip/backwardcompat/ssl_match_hostname.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""The match_hostname() function from Python 3.2, essential when using SSL."""

import re

__version__ = '3.2a3'

class CertificateError(ValueError):
pass

def _dnsname_to_pat(dn):
pats = []
for frag in dn.split(r'.'):
if frag == '*':
# When '*' is a fragment by itself, it matches a non-empty dotless
# fragment.
pats.append('[^.]+')
else:
# Otherwise, '*' matches any dotless fragment.
frag = re.escape(frag)
pats.append(frag.replace(r'\*', '[^.]*'))
return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)

def match_hostname(cert, hostname):
"""Verify that *cert* (in decoded format as returned by
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
are mostly followed, but IP addresses are not accepted for *hostname*.

CertificateError is raised on failure. On success, the function
returns nothing.
"""
if not cert:
raise ValueError("empty or no certificate")
dnsnames = []
san = cert.get('subjectAltName', ())
for key, value in san:
if key == 'DNS':
if _dnsname_to_pat(value).match(hostname):
return
dnsnames.append(value)
if not san:
# The subject is only checked when subjectAltName is empty
for sub in cert.get('subject', ()):
for key, value in sub:
# XXX according to RFC 2818, the most specific Common Name
# must be used.
if key == 'commonName':
if _dnsname_to_pat(value).match(hostname):
return
dnsnames.append(value)
if len(dnsnames) > 1:
raise CertificateError("hostname %r "
"doesn't match either of %s"
% (hostname, ', '.join(map(repr, dnsnames))))
elif len(dnsnames) == 1:
raise CertificateError("hostname %r "
"doesn't match %r"
% (hostname, dnsnames[0]))
else:
raise CertificateError("no appropriate commonName or "
"subjectAltName fields were found")
Loading