##// END OF EJS Templates
store: treat range as a generator instead of a list for py3 compat
store: treat range as a generator instead of a list for py3 compat

File last commit:

r29042:693b856a stable
r29071:2f58975e default
Show More
sslutil.py
317 lines | 11.8 KiB | text/x-python | PythonLexer
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204 # sslutil.py - SSL handling for mercurial
#
# Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
# Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
Gregory Szorc
sslutil: use absolute_import
r25977
from __future__ import absolute_import
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204
Gregory Szorc
sslutil: use absolute_import
r25977 import os
import ssl
import sys
from .i18n import _
Gregory Szorc
sslutil: use preferred formatting for import syntax
r28577 from . import (
error,
util,
)
Yuya Nishihara
ssl: load CA certificates from system's store by default on Python 2.7.9...
r24291
Gregory Szorc
sslutil: better document state of security/ssl module...
r28647 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
# support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
# all exposed via the "ssl" module.
#
# Depending on the version of Python being used, SSL/TLS support is either
# modern/secure or legacy/insecure. Many operations in this module have
# separate code paths depending on support in Python.
Gregory Szorc
sslutil: expose attribute indicating whether SNI is supported...
r26622 hassni = getattr(ssl, 'HAS_SNI', False)
Gregory Szorc
sslutil: store OP_NO_SSL* constants in module scope...
r28648 try:
OP_NO_SSLv2 = ssl.OP_NO_SSLv2
OP_NO_SSLv3 = ssl.OP_NO_SSLv3
except AttributeError:
OP_NO_SSLv2 = 0x1000000
OP_NO_SSLv3 = 0x2000000
Gregory Szorc
sslutil: implement SSLContext class...
r28649 try:
# ssl.SSLContext was added in 2.7.9 and presence indicates modern
# SSL/TLS features are available.
SSLContext = ssl.SSLContext
modernssl = True
Gregory Szorc
sslutil: move _canloaddefaultcerts logic...
r28650 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
Gregory Szorc
sslutil: implement SSLContext class...
r28649 except AttributeError:
modernssl = False
Gregory Szorc
sslutil: move _canloaddefaultcerts logic...
r28650 _canloaddefaultcerts = False
Gregory Szorc
sslutil: implement SSLContext class...
r28649
# We implement SSLContext using the interface from the standard library.
class SSLContext(object):
# ssl.wrap_socket gained the "ciphers" named argument in 2.7.
_supportsciphers = sys.version_info >= (2, 7)
def __init__(self, protocol):
# From the public interface of SSLContext
self.protocol = protocol
self.check_hostname = False
self.options = 0
self.verify_mode = ssl.CERT_NONE
# Used by our implementation.
self._certfile = None
self._keyfile = None
self._certpassword = None
self._cacerts = None
self._ciphers = None
def load_cert_chain(self, certfile, keyfile=None, password=None):
self._certfile = certfile
self._keyfile = keyfile
self._certpassword = password
def load_default_certs(self, purpose=None):
pass
def load_verify_locations(self, cafile=None, capath=None, cadata=None):
if capath:
raise error.Abort('capath not supported')
if cadata:
raise error.Abort('cadata not supported')
self._cacerts = cafile
def set_ciphers(self, ciphers):
if not self._supportsciphers:
raise error.Abort('setting ciphers not supported')
self._ciphers = ciphers
def wrap_socket(self, socket, server_hostname=None, server_side=False):
# server_hostname is unique to SSLContext.wrap_socket and is used
# for SNI in that context. So there's nothing for us to do with it
# in this legacy code since we don't support SNI.
args = {
'keyfile': self._keyfile,
'certfile': self._certfile,
'server_side': server_side,
'cert_reqs': self.verify_mode,
'ssl_version': self.protocol,
'ca_certs': self._cacerts,
}
if self._supportsciphers:
args['ciphers'] = self._ciphers
return ssl.wrap_socket(socket, **args)
Gregory Szorc
sslutil: remove indentation in wrapsocket declaration...
r28652 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
ca_certs=None, serverhostname=None):
Gregory Szorc
sslutil: add docstring to wrapsocket()...
r28653 """Add SSL/TLS to a socket.
This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
choices based on what security options are available.
In addition to the arguments supported by ``ssl.wrap_socket``, we allow
the following additional arguments:
* serverhostname - The expected hostname of the remote server. If the
server (and client) support SNI, this tells the server which certificate
to use.
"""
Gregory Szorc
sslutil: remove indentation in wrapsocket declaration...
r28652 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
# that both ends support, including TLS protocols. On legacy stacks,
# the highest it likely goes in TLS 1.0. On modern stacks, it can
# support TLS 1.2.
#
# The PROTOCOL_TLSv* constants select a specific TLS version
# only (as opposed to multiple versions). So the method for
# supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
# disable protocols via SSLContext.options and OP_NO_* constants.
# However, SSLContext.options doesn't work unless we have the
# full/real SSLContext available to us.
#
# SSLv2 and SSLv3 are broken. We ban them outright.
if modernssl:
protocol = ssl.PROTOCOL_SSLv23
else:
protocol = ssl.PROTOCOL_TLSv1
Gregory Szorc
sslutil: always use SSLContext...
r28651
Gregory Szorc
sslutil: remove indentation in wrapsocket declaration...
r28652 # TODO use ssl.create_default_context() on modernssl.
sslcontext = SSLContext(protocol)
Gregory Szorc
sslutil: always use SSLContext...
r28651
Gregory Szorc
sslutil: remove indentation in wrapsocket declaration...
r28652 # This is a no-op on old Python.
sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
Gregory Szorc
sslutil: always use SSLContext...
r28651
Gregory Szorc
sslutil: move and document verify_mode assignment...
r28848 # This still works on our fake SSLContext.
sslcontext.verify_mode = cert_reqs
Gregory Szorc
sslutil: remove indentation in wrapsocket declaration...
r28652 if certfile is not None:
def password():
f = keyfile or certfile
return ui.getpass(_('passphrase for %s: ') % f, '')
sslcontext.load_cert_chain(certfile, keyfile, password)
Gregory Szorc
sslutil: move and document verify_mode assignment...
r28848
Gregory Szorc
sslutil: remove indentation in wrapsocket declaration...
r28652 if ca_certs is not None:
sslcontext.load_verify_locations(cafile=ca_certs)
else:
# This is a no-op on old Python.
sslcontext.load_default_certs()
Alex Orange
https: support tls sni (server name indication) for https urls (issue3090)...
r23834
Gregory Szorc
sslutil: remove indentation in wrapsocket declaration...
r28652 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
# check if wrap_socket failed silently because socket had been
# closed
# - see http://bugs.python.org/issue13721
if not sslsocket.cipher():
raise error.Abort(_('ssl connection failed'))
return sslsocket
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204
def _verifycert(cert, hostname):
'''Verify that cert (in socket.getpeercert() format) matches hostname.
CRLs is not handled.
Returns error message if any problems are found and None on success.
'''
if not cert:
return _('no certificate received')
dnsname = hostname.lower()
def matchdnsname(certname):
return (certname == dnsname or
'.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
san = cert.get('subjectAltName', [])
if san:
certnames = [value.lower() for key, value in san if key == 'DNS']
for name in certnames:
if matchdnsname(name):
return None
Nicolas Bareil
sslutil: fall back to commonName when no dNSName in subjectAltName (issue2798)...
r14666 if certnames:
return _('certificate is for %s') % ', '.join(certnames)
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204
# subject is only checked when subjectAltName is empty
for s in cert.get('subject', []):
key, value = s[0]
if key == 'commonName':
try:
# 'subject' entries are unicode
certname = value.lower().encode('ascii')
except UnicodeEncodeError:
return _('IDN in certificate not supported')
if matchdnsname(certname):
return None
return _('certificate is for %s') % certname
return _('no commonName or subjectAltName found in certificate')
# CERT_REQUIRED means fetch the cert from the server all the time AND
# validate it against the CA store provided in web.cacerts.
Mads Kiilerich
ssl: only use the dummy cert hack if using an Apple Python (issue4410)...
r23042 def _plainapplepython():
"""return true if this seems to be a pure Apple Python that
* is unfrozen and presumably has the whole mercurial module in the file
system
* presumably is an Apple Python that uses Apple OpenSSL which has patches
for using system certificate store CAs in addition to the provided
cacerts file
"""
Yuya Nishihara
ssl: resolve symlink before checking for Apple python executable (issue4588)...
r24614 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
Mads Kiilerich
ssl: only use the dummy cert hack if using an Apple Python (issue4410)...
r23042 return False
Yuya Nishihara
ssl: resolve symlink before checking for Apple python executable (issue4588)...
r24614 exe = os.path.realpath(sys.executable).lower()
Mads Kiilerich
ssl: only use the dummy cert hack if using an Apple Python (issue4410)...
r23042 return (exe.startswith('/usr/bin/python') or
exe.startswith('/system/library/frameworks/python.framework/'))
Yuya Nishihara
ssl: extract function that returns dummycert path on Apple python...
r24288 def _defaultcacerts():
Yuya Nishihara
ssl: load CA certificates from system's store by default on Python 2.7.9...
r24291 """return path to CA certificates; None for system's store; ! to disable"""
Yuya Nishihara
ssl: extract function that returns dummycert path on Apple python...
r24288 if _plainapplepython():
dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
if os.path.exists(dummycert):
return dummycert
Yuya Nishihara
ssl: load CA certificates from system's store by default on Python 2.7.9...
r24291 if _canloaddefaultcerts:
return None
Yuya Nishihara
ssl: set explicit symbol "!" to web.cacerts to disable SSL verification (BC)...
r24290 return '!'
Yuya Nishihara
ssl: extract function that returns dummycert path on Apple python...
r24288
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204 def sslkwargs(ui, host):
Yuya Nishihara
ssl: prompt passphrase of client key file via ui.getpass() (issue4648)...
r25415 kws = {'ui': ui}
Mads Kiilerich
ssl: refactor sslkwargs - move things around a bit, preparing for next change
r22574 hostfingerprint = ui.config('hostfingerprints', host)
if hostfingerprint:
return kws
cacerts = ui.config('web', 'cacerts')
Yuya Nishihara
ssl: set explicit symbol "!" to web.cacerts to disable SSL verification (BC)...
r24290 if cacerts == '!':
pass
elif cacerts:
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204 cacerts = util.expandpath(cacerts)
if not os.path.exists(cacerts):
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
Yuya Nishihara
ssl: set explicit symbol "!" to web.cacerts to disable SSL verification (BC)...
r24290 else:
cacerts = _defaultcacerts()
if cacerts and cacerts != '!':
ui.debug('using %s to enable OS X system CA\n' % cacerts)
ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
if cacerts != '!':
Augie Fackler
sslutil: add a config knob to support TLS (default) or SSLv23 (bc) (issue4038)...
r19806 kws.update({'ca_certs': cacerts,
Yuya Nishihara
ssl: remove CERT_REQUIRED constant that was necessary for compatibility
r25432 'cert_reqs': ssl.CERT_REQUIRED,
Augie Fackler
sslutil: add a config knob to support TLS (default) or SSLv23 (bc) (issue4038)...
r19806 })
return kws
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204
class validator(object):
def __init__(self, ui, host):
self.ui = ui
self.host = host
FUJIWARA Katsunori
sslutil: abort if peer certificate is not verified for secure use...
r18887 def __call__(self, sock, strict=False):
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204 host = self.host
Matt Mackall
sslutil: try harder to avoid getpeercert problems...
r18879
Mads Kiilerich
sslutil: work around validator crash getting certificate on failed sockets...
r15816 if not sock.cipher(): # work around http://bugs.python.org/issue13721
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('%s ssl connection error') % host)
Matt Mackall
sslutil: try harder to avoid getpeercert problems...
r18879 try:
peercert = sock.getpeercert(True)
peercert2 = sock.getpeercert()
except AttributeError:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('%s ssl connection error') % host)
Matt Mackall
sslutil: try harder to avoid getpeercert problems...
r18879
Mads Kiilerich
sslutil: abort properly if no certificate received for https connection...
r15817 if not peercert:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('%s certificate error: '
Mads Kiilerich
sslutil: abort properly if no certificate received for https connection...
r15817 'no certificate received') % host)
Gregory Szorc
sslutil: document and slightly refactor validation logic...
r28850
# If a certificate fingerprint is pinned, use it and only it to
# validate the remote cert.
hostfingerprints = self.ui.configlist('hostfingerprints', host)
Mads Kiilerich
sslutil: show fingerprint when cacerts validation fails
r15814 peerfingerprint = util.sha1(peercert).hexdigest()
nicefingerprint = ":".join([peerfingerprint[x:x + 2]
for x in xrange(0, len(peerfingerprint), 2)])
Gregory Szorc
sslutil: allow multiple fingerprints per host...
r28525 if hostfingerprints:
fingerprintmatch = False
for hostfingerprint in hostfingerprints:
if peerfingerprint.lower() == \
hostfingerprint.replace(':', '').lower():
fingerprintmatch = True
break
if not fingerprintmatch:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('certificate for %s has unexpected '
Matt Mackall
sslutil: more helpful fingerprint mismatch message...
r15997 'fingerprint %s') % (host, nicefingerprint),
hint=_('check hostfingerprint configuration'))
Mads Kiilerich
sslutil: reorder validator code to make it more readable
r15815 self.ui.debug('%s certificate matched fingerprint %s\n' %
(host, nicefingerprint))
Gregory Szorc
sslutil: document and slightly refactor validation logic...
r28850 return
# No pinned fingerprint. Establish trust by looking at the CAs.
cacerts = self.ui.config('web', 'cacerts')
if cacerts != '!':
Matt Mackall
sslutil: try harder to avoid getpeercert problems...
r18879 msg = _verifycert(peercert2, host)
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204 if msg:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('%s certificate error: %s') % (host, msg),
Mads Kiilerich
sslutil: show fingerprint when cacerts validation fails
r15814 hint=_('configure hostfingerprint %s or use '
'--insecure to connect insecurely') %
nicefingerprint)
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204 self.ui.debug('%s certificate successfully verified\n' % host)
FUJIWARA Katsunori
sslutil: abort if peer certificate is not verified for secure use...
r18887 elif strict:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('%s certificate with fingerprint %s not '
FUJIWARA Katsunori
sslutil: abort if peer certificate is not verified for secure use...
r18887 'verified') % (host, nicefingerprint),
hint=_('check hostfingerprints or web.cacerts '
'config setting'))
Augie Fackler
sslutil: extracted ssl methods from httpsconnection in url.py...
r14204 else:
Mads Kiilerich
sslutil: reorder validator code to make it more readable
r15815 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
'verified (check hostfingerprints or web.cacerts '
'config setting)\n') %
(host, nicefingerprint))