-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Changes from 15 commits
b9ea089
246e974
db79f83
39d84ab
d54c695
7776cfc
b97969f
6bd3bd4
11ebe01
c612db0
73c4614
4bb5ac6
7e20fd8
b2e0b6d
a4a9197
d50a3b7
7fac01a
8496406
226768d
193562b
d8fe463
456ea80
fe17bb4
d77380d
609cfe9
6dc3212
bb7ba1a
e338436
857f64e
49563cf
f92052f
76b5ebc
83d8b37
912784f
9cda4d6
8fc02eb
1cf1a7e
a6a0ee3
84f8134
f13f879
9b57f89
7c8470a
d0c3138
809a996
22bf924
b2a17e5
1857ece
c4753b2
26f9c14
f72ddaa
a2ba2dc
e3f1ce8
559d77a
4a4a141
039e1fc
2cbc7fa
889e1a0
db9c92a
d6bb9a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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") |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,10 @@ | |
import zipfile | ||
import tarfile | ||
import subprocess | ||
import textwrap | ||
from pip.exceptions import InstallationError, BadCommand | ||
from pip.backwardcompat import WindowsError, string_types, raw_input, console_to_str, user_site | ||
from pip.backwardcompat import(WindowsError, string_types, raw_input, | ||
console_to_str, user_site, ssl) | ||
from pip.locations import site_packages, running_under_virtualenv, virtualenv_no_global | ||
from pip.log import logger | ||
|
||
|
@@ -664,3 +666,18 @@ def call_subprocess(cmd, show_stdout=True, | |
% (command_desc, proc.returncode, cwd)) | ||
if stdout is not None: | ||
return ''.join(all_output) | ||
|
||
|
||
def warn_if_no_ssl(): | ||
"""Warn when there's no ssl""" | ||
if not ssl: | ||
logger.warn(textwrap.dedent(""" | ||
############################################################# | ||
## WARNING!! ## | ||
## You don't have an importable ssl module. ## | ||
## We can not provide ssl certified downloads from PyPI. ## | ||
## Install this: http://pypi.python.org/pypi/ssl/ ## | ||
## It provides ssl support for older Pythons. ## | ||
############################################################# | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is pretty good already, I fear that people will freak out if they see it for the first time though. Let's explain why we can't ship ssl support and link to our documentation explaining in detail how to install it. |
||
""")) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to use "https" in this URL :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes : )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an additional note, I think that we should probably wait for a keypress, or wait either 10 seconds or a keypress, (or exit and ask to be relaunched with a command line option, eg:
--exploit-me
). I know it's annoying, but there is a remote code execution vulnerability here; if you simply print a warning and get on downloading the file and running its setup.py, it might be too late for the user.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll go with the consensus, but I was concerned about breaking people's automated processes. @jezdez, @pnasrat ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that's an (understandable) concern, a delay of 10 seconds (shortened by a keypress) should probably solve it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like
--exploit-me
as an option just--nossl
is probably sufficient. We probably don't want to break scripts, just announce vocally.For package maintainers we should also have this in the installation docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pnasrat but if they don't use
--nossl
?which option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they don't explictly disable SSL fail w/ an error telling them how to bypass it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.