mercurial/sslutil.py
changeset 29559 7dec5e441bf7
parent 29558 a935cd7d51a6
child 29560 303e9300772a
equal deleted inserted replaced
29558:a935cd7d51a6 29559:7dec5e441bf7
    27 #
    27 #
    28 # Depending on the version of Python being used, SSL/TLS support is either
    28 # Depending on the version of Python being used, SSL/TLS support is either
    29 # modern/secure or legacy/insecure. Many operations in this module have
    29 # modern/secure or legacy/insecure. Many operations in this module have
    30 # separate code paths depending on support in Python.
    30 # separate code paths depending on support in Python.
    31 
    31 
       
    32 configprotocols = set([
       
    33     'tls1.0',
       
    34     'tls1.1',
       
    35     'tls1.2',
       
    36 ])
       
    37 
    32 hassni = getattr(ssl, 'HAS_SNI', False)
    38 hassni = getattr(ssl, 'HAS_SNI', False)
    33 
       
    34 try:
       
    35     OP_NO_SSLv2 = ssl.OP_NO_SSLv2
       
    36     OP_NO_SSLv3 = ssl.OP_NO_SSLv3
       
    37 except AttributeError:
       
    38     OP_NO_SSLv2 = 0x1000000
       
    39     OP_NO_SSLv3 = 0x2000000
       
    40 
    39 
    41 try:
    40 try:
    42     # ssl.SSLContext was added in 2.7.9 and presence indicates modern
    41     # ssl.SSLContext was added in 2.7.9 and presence indicates modern
    43     # SSL/TLS features are available.
    42     # SSL/TLS features are available.
    44     SSLContext = ssl.SSLContext
    43     SSLContext = ssl.SSLContext
   134         'ctxoptions': None,
   133         'ctxoptions': None,
   135     }
   134     }
   136 
   135 
   137     # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
   136     # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
   138     # that both ends support, including TLS protocols. On legacy stacks,
   137     # that both ends support, including TLS protocols. On legacy stacks,
   139     # the highest it likely goes in TLS 1.0. On modern stacks, it can
   138     # the highest it likely goes is TLS 1.0. On modern stacks, it can
   140     # support TLS 1.2.
   139     # support TLS 1.2.
   141     #
   140     #
   142     # The PROTOCOL_TLSv* constants select a specific TLS version
   141     # The PROTOCOL_TLSv* constants select a specific TLS version
   143     # only (as opposed to multiple versions). So the method for
   142     # only (as opposed to multiple versions). So the method for
   144     # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
   143     # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
   145     # disable protocols via SSLContext.options and OP_NO_* constants.
   144     # disable protocols via SSLContext.options and OP_NO_* constants.
   146     # However, SSLContext.options doesn't work unless we have the
   145     # However, SSLContext.options doesn't work unless we have the
   147     # full/real SSLContext available to us.
   146     # full/real SSLContext available to us.
   148     if modernssl:
   147 
   149         s['protocol'] = ssl.PROTOCOL_SSLv23
   148     # Allow minimum TLS protocol to be specified in the config.
   150     else:
   149     def validateprotocol(protocol, key):
   151         s['protocol'] = ssl.PROTOCOL_TLSv1
   150         if protocol not in configprotocols:
   152 
   151             raise error.Abort(
   153     # SSLv2 and SSLv3 are broken. We ban them outright.
   152                 _('unsupported protocol from hostsecurity.%s: %s') %
   154     # WARNING: ctxoptions doesn't have an effect unless the modern ssl module
   153                 (key, protocol),
   155     # is available. Be careful when adding flags!
   154                 hint=_('valid protocols: %s') %
   156     s['ctxoptions'] = OP_NO_SSLv2 | OP_NO_SSLv3
   155                      ' '.join(sorted(configprotocols)))
   157 
   156 
   158     # Prevent CRIME.
   157     key = 'minimumprotocol'
   159     # There is no guarantee this attribute is defined on the module.
   158     # Default to TLS 1.0+ as that is what browsers are currently doing.
   160     s['ctxoptions'] |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
   159     protocol = ui.config('hostsecurity', key, 'tls1.0')
       
   160     validateprotocol(protocol, key)
       
   161 
       
   162     key = '%s:minimumprotocol' % hostname
       
   163     protocol = ui.config('hostsecurity', key, protocol)
       
   164     validateprotocol(protocol, key)
       
   165 
       
   166     s['protocol'], s['ctxoptions'] = protocolsettings(protocol)
   161 
   167 
   162     # Look for fingerprints in [hostsecurity] section. Value is a list
   168     # Look for fingerprints in [hostsecurity] section. Value is a list
   163     # of <alg>:<fingerprint> strings.
   169     # of <alg>:<fingerprint> strings.
   164     fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
   170     fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
   165                                  [])
   171                                  [])
   248     assert s['ctxoptions'] is not None
   254     assert s['ctxoptions'] is not None
   249     assert s['verifymode'] is not None
   255     assert s['verifymode'] is not None
   250 
   256 
   251     return s
   257     return s
   252 
   258 
       
   259 def protocolsettings(protocol):
       
   260     """Resolve the protocol and context options for a config value."""
       
   261     if protocol not in configprotocols:
       
   262         raise ValueError('protocol value not supported: %s' % protocol)
       
   263 
       
   264     # Legacy ssl module only supports up to TLS 1.0. Ideally we'd use
       
   265     # PROTOCOL_SSLv23 and options to disable SSLv2 and SSLv3. However,
       
   266     # SSLContext.options doesn't work in our implementation since we use
       
   267     # a fake SSLContext on these Python versions.
       
   268     if not modernssl:
       
   269         if protocol != 'tls1.0':
       
   270             raise error.Abort(_('current Python does not support protocol '
       
   271                                 'setting %s') % protocol,
       
   272                               hint=_('upgrade Python or disable setting since '
       
   273                                      'only TLS 1.0 is supported'))
       
   274 
       
   275         return ssl.PROTOCOL_TLSv1, 0
       
   276 
       
   277     # WARNING: returned options don't work unless the modern ssl module
       
   278     # is available. Be careful when adding options here.
       
   279 
       
   280     # SSLv2 and SSLv3 are broken. We ban them outright.
       
   281     options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
       
   282 
       
   283     if protocol == 'tls1.0':
       
   284         # Defaults above are to use TLS 1.0+
       
   285         pass
       
   286     elif protocol == 'tls1.1':
       
   287         options |= ssl.OP_NO_TLSv1
       
   288     elif protocol == 'tls1.2':
       
   289         options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
       
   290     else:
       
   291         raise error.Abort(_('this should not happen'))
       
   292 
       
   293     # Prevent CRIME.
       
   294     # There is no guarantee this attribute is defined on the module.
       
   295     options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
       
   296 
       
   297     return ssl.PROTOCOL_SSLv23, options
       
   298 
   253 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
   299 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
   254     """Add SSL/TLS to a socket.
   300     """Add SSL/TLS to a socket.
   255 
   301 
   256     This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
   302     This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
   257     choices based on what security options are available.
   303     choices based on what security options are available.
   304     else:
   350     else:
   305         caloaded = False
   351         caloaded = False
   306 
   352 
   307     try:
   353     try:
   308         sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
   354         sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
   309     except ssl.SSLError:
   355     except ssl.SSLError as e:
   310         # If we're doing certificate verification and no CA certs are loaded,
   356         # If we're doing certificate verification and no CA certs are loaded,
   311         # that is almost certainly the reason why verification failed. Provide
   357         # that is almost certainly the reason why verification failed. Provide
   312         # a hint to the user.
   358         # a hint to the user.
   313         # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
   359         # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
   314         # only show this warning if modern ssl is available.
   360         # only show this warning if modern ssl is available.
   316             modernssl and not sslcontext.get_ca_certs()):
   362             modernssl and not sslcontext.get_ca_certs()):
   317             ui.warn(_('(an attempt was made to load CA certificates but none '
   363             ui.warn(_('(an attempt was made to load CA certificates but none '
   318                       'were loaded; see '
   364                       'were loaded; see '
   319                       'https://mercurial-scm.org/wiki/SecureConnections for '
   365                       'https://mercurial-scm.org/wiki/SecureConnections for '
   320                       'how to configure Mercurial to avoid this error)\n'))
   366                       'how to configure Mercurial to avoid this error)\n'))
       
   367         # Try to print more helpful error messages for known failures.
       
   368         if util.safehasattr(e, 'reason'):
       
   369             if e.reason == 'UNSUPPORTED_PROTOCOL':
       
   370                 ui.warn(_('(could not negotiate a common protocol; see '
       
   371                           'https://mercurial-scm.org/wiki/SecureConnections '
       
   372                           'for how to configure Mercurial to avoid this '
       
   373                           'error)\n'))
   321         raise
   374         raise
   322 
   375 
   323     # check if wrap_socket failed silently because socket had been
   376     # check if wrap_socket failed silently because socket had been
   324     # closed
   377     # closed
   325     # - see http://bugs.python.org/issue13721
   378     # - see http://bugs.python.org/issue13721
   347 
   400 
   348     ``requireclientcert`` specifies whether to require client certificates.
   401     ``requireclientcert`` specifies whether to require client certificates.
   349 
   402 
   350     Typically ``cafile`` is only defined if ``requireclientcert`` is true.
   403     Typically ``cafile`` is only defined if ``requireclientcert`` is true.
   351     """
   404     """
       
   405     protocol, options = protocolsettings('tls1.0')
       
   406 
       
   407     # This config option is intended for use in tests only. It is a giant
       
   408     # footgun to kill security. Don't define it.
       
   409     exactprotocol = ui.config('devel', 'serverexactprotocol')
       
   410     if exactprotocol == 'tls1.0':
       
   411         protocol = ssl.PROTOCOL_TLSv1
       
   412     elif exactprotocol == 'tls1.1':
       
   413         protocol = ssl.PROTOCOL_TLSv1_1
       
   414     elif exactprotocol == 'tls1.2':
       
   415         protocol = ssl.PROTOCOL_TLSv1_2
       
   416     elif exactprotocol:
       
   417         raise error.Abort(_('invalid value for serverexactprotocol: %s') %
       
   418                           exactprotocol)
       
   419 
   352     if modernssl:
   420     if modernssl:
   353         # We /could/ use create_default_context() here since it doesn't load
   421         # We /could/ use create_default_context() here since it doesn't load
   354         # CAs when configured for client auth.
   422         # CAs when configured for client auth. However, it is hard-coded to
   355         sslcontext = SSLContext(ssl.PROTOCOL_SSLv23)
   423         # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
   356         # SSLv2 and SSLv3 are broken. Ban them outright.
   424         sslcontext = SSLContext(protocol)
   357         sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
   425         sslcontext.options |= options
   358         # Prevent CRIME
   426 
   359         sslcontext.options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
       
   360         # Improve forward secrecy.
   427         # Improve forward secrecy.
   361         sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
   428         sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
   362         sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
   429         sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
   363 
   430 
   364         # Use the list of more secure ciphers if found in the ssl module.
   431         # Use the list of more secure ciphers if found in the ssl module.