@@ -109,39 +109,45 @@ except AttributeError:
def _hostsettings(ui, hostname):
"""Obtain security settings for a hostname.
Returns a dict of settings relevant to that hostname.
"""
s = {
# List of 2-tuple of (hash algorithm, hash).
'certfingerprints': [],
+ # ssl.CERT_* constant used by SSLContext.verify_mode.
+ 'verifymode': None,
}
# Fingerprints from [hostfingerprints] are always SHA-1.
for fingerprint in ui.configlist('hostfingerprints', hostname, []):
fingerprint = fingerprint.replace(':', '').lower()
s['certfingerprints'].append(('sha1', fingerprint))
+ # If a host cert fingerprint is defined, it is the only thing that
+ # matters. No need to validate CA certs.
+ if s['certfingerprints']:
+ s['verifymode'] = ssl.CERT_NONE
+
+ # If --insecure is used, don't take CAs into consideration.
+ elif ui.insecureconnections:
+ s['verifymode'] = ssl.CERT_NONE
+
+ # TODO assert verifymode is not None once we integrate cacert
+ # checking in this function.
+
return s
-def _determinecertoptions(ui, host):
+def _determinecertoptions(ui, settings):
"""Determine certificate options for a connections.
Returns a tuple of (cert_reqs, ca_certs).
"""
- # If a host key fingerprint is on file, it is the only thing that matters
- # and CA certs don't come into play.
- hostfingerprint = ui.config('hostfingerprints', host)
- if hostfingerprint:
- return ssl.CERT_NONE, None
-
- # The code below sets up CA verification arguments. If --insecure is
- # used, we don't take CAs into consideration, so return early.
- if ui.insecureconnections:
+ if settings['verifymode'] == ssl.CERT_NONE:
return ssl.CERT_NONE, None
cacerts = ui.config('web', 'cacerts')
# If a value is set in the config, validate against a path and load
# and require those certs.
if cacerts:
cacerts = util.expandpath(cacerts)
@@ -176,17 +182,18 @@ def wrapsocket(sock, keyfile, certfile,
* serverhostname - The expected hostname of the remote server. If the
server (and client) support SNI, this tells the server which certificate
to use.
"""
if not serverhostname:
raise error.Abort('serverhostname argument is required')
- cert_reqs, ca_certs = _determinecertoptions(ui, serverhostname)
+ settings = _hostsettings(ui, serverhostname)
+ cert_reqs, ca_certs = _determinecertoptions(ui, settings)
# 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
@@ -229,17 +236,17 @@ def wrapsocket(sock, keyfile, certfile,
# closed
# - see http://bugs.python.org/issue13721
if not sslsocket.cipher():
raise error.Abort(_('ssl connection failed'))
sslsocket._hgstate = {
'caloaded': caloaded,
'hostname': serverhostname,
- 'settings': _hostsettings(ui, serverhostname),
+ 'settings': settings,
'ui': ui,
}
return sslsocket
def _verifycert(cert, hostname):
'''Verify that cert (in socket.getpeercert() format) matches hostname.
CRLs is not handled.