Guest User

Untitled

a guest
Aug 26th, 2013
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.91 KB | None | 0 0
  1. this patch series consists of 1 patches.
  2.  
  3. [?1034hCc:
  4. displaying [PATCH] https: support tls sni (server name indication) for https urls (issue3090) ...
  5. Content-Type: text/plain; charset="us-ascii"
  6. MIME-Version: 1.0
  7. Content-Transfer-Encoding: 7bit
  8. Subject: [PATCH] https: support tls sni (server name indication) for https
  9. urls (issue3090)
  10. X-Mercurial-Node: bd1dc245a27e4a723f820c6261186022c28f74ac
  11. Message-Id: <bd1dc245a27e4a723f82.1377549437@localhost>
  12. User-Agent: Mercurial-patchbomb/2.4.2
  13. Date: Mon, 26 Aug 2013 14:37:17 -0600
  14. From: Alex Orange <[email protected]>
  15. Cc: q
  16.  
  17. # HG changeset patch
  18. # User Alex Orange <[email protected]>
  19. # Date 1377425168 21600
  20. # Node ID bd1dc245a27e4a723f820c6261186022c28f74ac
  21. # Parent d4a0055af149cdea20b3136b66cae8a24b2e2a98
  22. https: support tls sni (server name indication) for https urls (issue3090)
  23.  
  24. SNI is a common way of sharing servers across multiple domains using separate
  25. SSL certificates. Python 2.x does not, and will not, support SNI according to:
  26. http://bugs.python.org/issue5639#msg192234. In order to support SNI pyOpenSSL
  27. and pyasn1 have been used to emulate Python's SSLSocket object (the one
  28. returned by wrap_socket). Additionally, changes are made to code in httpclient,
  29. sslutil, and url to pass the server hostname to the relavent wrap_socket
  30. wrapper function.
  31.  
  32. The research I did led me to the conclusion that an OpenSSL based solution
  33. would best replicate that existing python ssl object. As opposed to a GNUTLS
  34. solution like PyGnuTLS (https://gitorious.org/pygnutls). The two packages I
  35. found that perform the basic functions of the python ssl object are pyOpenSSL
  36. (http://pythonhosted.org/pyOpenSSL/) and M2Crypto
  37. (http://www.heikkitoivonen.net/m2crypto/). M2Crypto does not support sending
  38. the host name as far as I can tell, which leaves pyOpenSSL. The shortcoming of
  39. both of these libraries is that they do not provide give the subjectAltName
  40. subobjects as separate objects. They give either the DER encoded raw data or
  41. an openssl command line style string "DNS:a.b.com, DNS:c.d.com, ...". I did not
  42. want to parse this string in case a certificate came up with a dNSName had the
  43. form 'a.b.com, DNS:c.d.com' which could conceivably lead to a security problem.
  44. In order to handle this problem I used pyasn1 to parse the subjectAltName data
  45. along with code taken from ndg-httpsclient. There appears to also be an
  46. way to do this directly through python's ssl module with an undocumented
  47. function called _test_decode_cert. However this would involve writing
  48. certificates to temporary files and then reading them back in. This seems like
  49. a bad idea both because the function is undocumented (and therefore vulnerable
  50. to going away without warning) and a possible security risk: writing to files
  51. and then reading them back in. Finally, to make the all of this as unobtrusive
  52. as possible I wrapped this functionality into a single class that emulates the
  53. python ssl class called SocketWrapper in opensslwrap.py.
  54.  
  55. Finally, imports of pyOpenSSL and pyasn1 are tried before the import of ssl in
  56. httpclient/socketutil.py and sslutil.py. This prefers the use of this scheme
  57. over the built-in ssl module for those that have the supporting packages.
  58.  
  59. diff -r d4a0055af149 -r bd1dc245a27e mercurial/httpclient/__init__.py
  60. --- a/mercurial/httpclient/__init__.py Fri Aug 23 16:16:22 2013 -0400
  61. +++ b/mercurial/httpclient/__init__.py Sun Aug 25 04:06:08 2013 -0600
  62. @@ -325,6 +325,8 @@
  63. raise Exception('ssl requested but unavailable on this Python')
  64. self.ssl = use_ssl
  65. self.ssl_opts = ssl_opts
  66. + if 'server_hostname' not in self.ssl_opts:
  67. + self.ssl_opts['server_hostname'] = self.host
  68. self._ssl_validator = ssl_validator
  69. self.host = host
  70. self.sock = None
  71. diff -r d4a0055af149 -r bd1dc245a27e mercurial/httpclient/opensslwrap.py
  72. --- /dev/null Thu Jan 01 00:00:00 1970 +0000
  73. +++ b/mercurial/httpclient/opensslwrap.py Sun Aug 25 04:06:08 2013 -0600
  74. @@ -0,0 +1,123 @@
  75. +import OpenSSL
  76. +import pyasn1.codec.der.decoder
  77. +import subjaltname
  78. +
  79. +import socket
  80. +
  81. +# Try to use python's builtin SSLError if we can, imitate it as best as we can
  82. +# if the import fails
  83. +try:
  84. + import ssl
  85. +except ImportError:
  86. + class ssl(object):
  87. + class SSLError(socket.error):
  88. + pass
  89. +
  90. +# Based heavily on code from:
  91. +# https://github.com/t-8ch/requests/blob/d7908a9fdef7bca16e384ca42478d69d1894c8b6/requests/packages/urllib3/contrib/pyopenssl.py check-code-ignore
  92. +CERT_NONE = OpenSSL.SSL.VERIFY_NONE
  93. +CERT_OPTIONAL = OpenSSL.SSL.VERIFY_PEER
  94. +CERT_REQUIRED = OpenSSL.SSL.VERIFY_PEER | \
  95. + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT
  96. +
  97. +_PROTOCOL_SSLv23 = 2
  98. +_PROTOCOL_SSLv3 = 1
  99. +_PROTOCOL_TLSv1 = 3
  100. +_openssl_versions = {
  101. + _PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD,
  102. + _PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD,
  103. + _PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
  104. +}
  105. +
  106. +class socketwrapper(object):
  107. + def __init__(self, connection, sock):
  108. + self.connection = connection
  109. + self.sock = sock
  110. +
  111. + def makefile(self, mode, bufsize=-1):
  112. + return socket._fileobject(self.connection, mode, bufsize)
  113. +
  114. + def getpeercert(self, binary_form=False):
  115. + x509 = self.connection.get_peer_certificate()
  116. + if not x509:
  117. + raise ssl.SSLError('')
  118. +
  119. + if binary_form:
  120. + return OpenSSL.crypto.dump_certificate(
  121. + OpenSSL.crypto.FILETYPE_ASN1,
  122. + x509)
  123. +
  124. + dns_name = []
  125. + general_names = subjaltname.SubjectAltName()
  126. +
  127. + for i in range(x509.get_extension_count()):
  128. + ext = x509.get_extension(i)
  129. + ext_name = ext.get_short_name()
  130. + if ext_name != 'subjectAltName':
  131. + continue
  132. +
  133. + ext_dat = ext.get_data()
  134. + der_decoder = pyasn1.codec.der.decoder
  135. + decoded_dat = der_decoder.decode(ext_dat,
  136. + asn1Spec=general_names)
  137. +
  138. + for name in decoded_dat:
  139. + if not isinstance(name, subjaltname.SubjectAltName):
  140. + continue
  141. + for entry in range(len(name)):
  142. + component = name.getComponentByPosition(entry)
  143. + if component.getName() != 'dNSName':
  144. + continue
  145. + dns_name.append(('DNS', str(component.getComponent())))
  146. +
  147. + return {
  148. + 'subject': (
  149. + (('commonName', x509.get_subject().CN),),
  150. + ),
  151. + 'subjectAltName': dns_name
  152. + }
  153. +
  154. + # Pass any unhandle function calls on to connection
  155. + def __getattr__(self, name):
  156. + try:
  157. + return getattr(self.connection, name)
  158. + except AttributeError:
  159. + return getattr(self.sock, name)
  160. +
  161. +class OpenSSLReformattedError(Exception):
  162. + def __init__(self, e):
  163. + self.e = e
  164. +
  165. + def __str__(self):
  166. + return '*:%s:%s (glob)'%(self.e.args[0][0][1], self.e.args[0][0][2])
  167. +
  168. +
  169. +def wrap_socket(sock, keyfile=None, certfile=None, server_side=False,
  170. + cert_reqs=CERT_NONE, ssl_version=_PROTOCOL_TLSv1,
  171. + ca_certs=None, do_handshake_on_connect=True,
  172. + suppress_ragged_eofs=True, server_hostname=None):
  173. + ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version])
  174. + if certfile:
  175. + ctx.use_certificate_file(certfile)
  176. + if keyfile:
  177. + ctx.use_privatekey_file(keyfile)
  178. + if cert_reqs != CERT_NONE:
  179. + ctx.set_verify(cert_reqs, lambda a, b, err_no, c, d: err_no == 0)
  180. + if ca_certs:
  181. + try:
  182. + ctx.load_verify_locations(ca_certs, None)
  183. + except OpenSSL.SSL.Error, e:
  184. + raise ssl.SSLError('bad ca_certs: %r' % ca_certs,
  185. + OpenSSLReformattedError(e))
  186. +
  187. + cnx = OpenSSL.SSL.Connection(ctx, sock)
  188. + if server_hostname is not None:
  189. + cnx.set_tlsext_host_name(server_hostname)
  190. + cnx.set_connect_state()
  191. + try:
  192. + cnx.do_handshake()
  193. + except OpenSSL.SSL.Error, e:
  194. + raise ssl.SSLError('bad handshake',
  195. + OpenSSLReformattedError(e))
  196. +
  197. + return socketwrapper(cnx, sock)
  198. diff -r d4a0055af149 -r bd1dc245a27e mercurial/httpclient/socketutil.py
  199. --- a/mercurial/httpclient/socketutil.py Fri Aug 23 16:16:22 2013 -0400
  200. +++ b/mercurial/httpclient/socketutil.py Sun Aug 25 04:06:08 2013 -0600
  201. @@ -38,15 +38,27 @@
  202. logger = logging.getLogger(__name__)
  203.  
  204. try:
  205. - import ssl
  206. - # make demandimporters load the module
  207. - ssl.wrap_socket # pylint: disable=W0104
  208. + # Required to successfully import opensslwrap
  209. + from OpenSSL import SSL
  210. + from pyasn1 import types
  211. +
  212. + import opensslwrap
  213. have_ssl = True
  214. + have_sni = True
  215. except ImportError:
  216. - import httplib
  217. - import urllib2
  218. - have_ssl = getattr(urllib2, 'HTTPSHandler', False)
  219. - ssl = False
  220. + opensslwrap = False
  221. + try:
  222. + import ssl
  223. + # make demandimporters load the module
  224. + ssl.wrap_socket # pylint: disable=W0104
  225. + have_ssl = True
  226. + have_sni = False
  227. + except ImportError:
  228. + import httplib
  229. + import urllib2
  230. + have_ssl = getattr(urllib2, 'HTTPSHandler', False)
  231. + have_sni = False
  232. + ssl = False
  233.  
  234.  
  235. try:
  236. @@ -75,8 +87,18 @@
  237. raise socket.error(msg)
  238. return sock
  239.  
  240. -if ssl:
  241. - wrap_socket = ssl.wrap_socket
  242. +if opensslwrap:
  243. + CERT_NONE = opensslwrap.CERT_NONE
  244. + CERT_OPTIONAL = opensslwrap.CERT_OPTIONAL
  245. + CERT_REQUIRED = opensslwrap.CERT_REQUIRED
  246. +
  247. + wrap_socket = opensslwrap.wrap_socket
  248. +
  249. +elif ssl:
  250. + # Throw away the server hostname since Python 2.7 hasn't a clue about SNI
  251. + def wrap_socket(server_hostname=None, *args, **kwargs):
  252. + return ssl.wrap_socket(*args, **kwargs)
  253. +
  254. CERT_NONE = ssl.CERT_NONE
  255. CERT_OPTIONAL = ssl.CERT_OPTIONAL
  256. CERT_REQUIRED = ssl.CERT_REQUIRED
  257. @@ -120,7 +142,7 @@
  258. server_side=False, cert_reqs=CERT_NONE,
  259. ssl_version=_PROTOCOL_SSLv3, ca_certs=None,
  260. do_handshake_on_connect=True,
  261. - suppress_ragged_eofs=True):
  262. + suppress_ragged_eofs=True, server_hostname=None):
  263. """Backport of ssl.wrap_socket from Python 2.6."""
  264. if cert_reqs != CERT_NONE and ca_certs:
  265. raise CertificateValidationUnsupported(
  266. diff -r d4a0055af149 -r bd1dc245a27e mercurial/httpclient/subjaltname.py
  267. --- /dev/null Thu Jan 01 00:00:00 1970 +0000
  268. +++ b/mercurial/httpclient/subjaltname.py Sun Aug 25 04:06:08 2013 -0600
  269. @@ -0,0 +1,124 @@
  270. +"""NDG HTTPS Client package
  271. +
  272. +Use pyasn1 to provide support for parsing ASN.1 formatted subjectAltName
  273. +content for SSL peer verification. Code based on:
  274. +
  275. +http://stackoverflow.com/questions/5519958/how-do-i-parse-subjectaltname-extension-data-using-pyasn1
  276. +"""
  277. +__author__ = "P J Kershaw"
  278. +__date__ = "01/02/12"
  279. +__copyright__ = "(C) 2012 Science and Technology Facilities Council"
  280. +__license__ = "BSD - see LICENSE file in top-level directory"
  281. +__contact__ = "[email protected]"
  282. +__revision__ = '$Id$'
  283. +
  284. +from pyasn1.type import univ, constraint, char, namedtype, tag
  285. +
  286. +
  287. +class DirectoryString(univ.Choice):
  288. + """ASN.1 Directory string class"""
  289. + componentType = namedtype.NamedTypes(
  290. + namedtype.NamedType(
  291. + 'teletexString', char.TeletexString()),
  292. + namedtype.NamedType(
  293. + 'printableString', char.PrintableString()),
  294. + namedtype.NamedType(
  295. + 'universalString', char.UniversalString()),
  296. + namedtype.NamedType(
  297. + 'utf8String', char.UTF8String()),
  298. + namedtype.NamedType(
  299. + 'bmpString', char.BMPString()),
  300. + namedtype.NamedType(
  301. + 'ia5String', char.IA5String()),
  302. + )
  303. +
  304. +
  305. +class AttributeValue(DirectoryString):
  306. + """ASN.1 Attribute value"""
  307. +
  308. +
  309. +class AttributeType(univ.ObjectIdentifier):
  310. + """ASN.1 Attribute type"""
  311. +
  312. +
  313. +class AttributeTypeAndValue(univ.Sequence):
  314. + """ASN.1 Attribute type and value class"""
  315. + componentType = namedtype.NamedTypes(
  316. + namedtype.NamedType('type', AttributeType()),
  317. + namedtype.NamedType('value', AttributeValue()),
  318. + )
  319. +
  320. +
  321. +class RelativeDistinguishedName(univ.SetOf):
  322. + '''ASN.1 Realtive distinguished name'''
  323. + componentType = AttributeTypeAndValue()
  324. +
  325. +class RDNSequence(univ.SequenceOf):
  326. + '''ASN.1 RDN sequence class'''
  327. + componentType = RelativeDistinguishedName()
  328. +
  329. +
  330. +class Name(univ.Choice):
  331. + '''ASN.1 name class'''
  332. + componentType = namedtype.NamedTypes(
  333. + namedtype.NamedType('', RDNSequence()),
  334. + )
  335. +
  336. +
  337. +class Extension(univ.Sequence):
  338. + '''ASN.1 extension class'''
  339. + componentType = namedtype.NamedTypes(
  340. + namedtype.NamedType('extnID', univ.ObjectIdentifier()),
  341. + namedtype.DefaultedNamedType('critical', univ.Boolean('False')),
  342. + namedtype.NamedType('extnValue', univ.OctetString()),
  343. + )
  344. +
  345. +
  346. +class Extensions(univ.SequenceOf):
  347. + '''ASN.1 extensions class'''
  348. + componentType = Extension()
  349. + sizeSpec = univ.SequenceOf.sizeSpec
  350. +
  351. +
  352. +class GeneralName(univ.Choice):
  353. + '''ASN.1 configuration for X.509 certificate subjectAltNames fields'''
  354. + componentType = namedtype.NamedTypes(
  355. +# namedtype.NamedType('otherName', AnotherName().subtype(
  356. +# implicitTag=tag.Tag(tag.tagClassContext,
  357. +# tag.tagFormatSimple, 0))),
  358. + namedtype.NamedType('rfc822Name', char.IA5String().subtype(
  359. + implicitTag=tag.Tag(tag.tagClassContext,
  360. + tag.tagFormatSimple, 1))),
  361. + namedtype.NamedType('dNSName', char.IA5String().subtype(
  362. + implicitTag=tag.Tag(tag.tagClassContext,
  363. + tag.tagFormatSimple, 2))),
  364. +# namedtype.NamedType('x400Address', ORAddress().subtype(
  365. +# implicitTag=tag.Tag(tag.tagClassContext,
  366. +# tag.tagFormatSimple, 3))),
  367. + namedtype.NamedType('directoryName', Name().subtype(
  368. + implicitTag=tag.Tag(tag.tagClassContext,
  369. + tag.tagFormatSimple, 4))),
  370. +# namedtype.NamedType('ediPartyName', EDIPartyName().subtype(
  371. +# implicitTag=tag.Tag(tag.tagClassContext,
  372. +# tag.tagFormatSimple, 5))),
  373. + namedtype.NamedType('uniformResourceIdentifier', char.IA5String().\
  374. + subtype(implicitTag=tag.Tag(tag.tagClassContext,
  375. + tag.tagFormatSimple, 6))),
  376. + namedtype.NamedType('iPAddress', univ.OctetString().subtype(
  377. + implicitTag=tag.Tag(tag.tagClassContext,
  378. + tag.tagFormatSimple, 7))),
  379. + namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype(
  380. + implicitTag=tag.Tag(tag.tagClassContext,
  381. + tag.tagFormatSimple, 8))),
  382. + )
  383. +
  384. +
  385. +class GeneralNames(univ.SequenceOf):
  386. + '''Sequence of names for ASN.1 subjectAltNames settings'''
  387. + componentType = GeneralName()
  388. + sizeSpec = univ.SequenceOf.sizeSpec
  389. +
  390. +
  391. +class SubjectAltName(GeneralNames):
  392. + '''ASN.1 implementation for subjectAltNames support'''
  393. +# no-check-code
  394. diff -r d4a0055af149 -r bd1dc245a27e mercurial/sslutil.py
  395. --- a/mercurial/sslutil.py Fri Aug 23 16:16:22 2013 -0400
  396. +++ b/mercurial/sslutil.py Sun Aug 25 04:06:08 2013 -0600
  397. @@ -11,34 +11,53 @@
  398. from mercurial import util
  399. from mercurial.i18n import _
  400. try:
  401. - # avoid using deprecated/broken FakeSocket in python 2.6
  402. - import ssl
  403. - CERT_REQUIRED = ssl.CERT_REQUIRED
  404. + # Required to successfully import opensslwrap
  405. + from OpenSSL import SSL
  406. + from pyasn1 import types
  407. +
  408. + import httpclient.opensslwrap
  409. + CERT_REQUIRED = httpclient.opensslwrap.CERT_REQUIRED
  410. def ssl_wrap_socket(sock, keyfile, certfile,
  411. - cert_reqs=ssl.CERT_NONE, ca_certs=None):
  412. - sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
  413. - cert_reqs=cert_reqs, ca_certs=ca_certs,
  414. - ssl_version=ssl.PROTOCOL_SSLv3)
  415. - # check if wrap_socket failed silently because socket had been closed
  416. - # - see http://bugs.python.org/issue13721
  417. - if not sslsocket.cipher():
  418. - raise util.Abort(_('ssl connection failed'))
  419. + cert_reqs=httpclient.opensslwrap.CERT_NONE,
  420. + ca_certs=None, server_hostname=None):
  421. + wrap_socket = httpclient.opensslwrap.wrap_socket
  422. + sslsocket = wrap_socket(sock, keyfile, certfile, cert_reqs=cert_reqs,
  423. + ca_certs=ca_certs,
  424. + server_hostname=server_hostname)
  425. return sslsocket
  426. except ImportError:
  427. - CERT_REQUIRED = 2
  428. + try:
  429. + # avoid using deprecated/broken FakeSocket in python 2.6
  430. + import ssl
  431. + CERT_REQUIRED = ssl.CERT_REQUIRED
  432. + def ssl_wrap_socket(sock, keyfile, certfile,
  433. + cert_reqs=ssl.CERT_NONE, ca_certs=None,
  434. + server_hostname=None):
  435. + sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
  436. + cert_reqs=cert_reqs, ca_certs=ca_certs,
  437. + ssl_version=ssl.PROTOCOL_SSLv3)
  438. + # check if wrap_socket failed silently because socket had been
  439. + # closed
  440. + # - see http://bugs.python.org/issue13721
  441. + if not sslsocket.cipher():
  442. + raise util.Abort(_('ssl connection failed'))
  443. + return sslsocket
  444. + except ImportError:
  445. + CERT_REQUIRED = 2
  446.  
  447. - import socket, httplib
  448. + import socket, httplib
  449.  
  450. - def ssl_wrap_socket(sock, key_file, cert_file,
  451. - cert_reqs=CERT_REQUIRED, ca_certs=None):
  452. - if not util.safehasattr(socket, 'ssl'):
  453. - raise util.Abort(_('Python SSL support not found'))
  454. - if ca_certs:
  455. - raise util.Abort(_(
  456. - 'certificate checking requires Python 2.6'))
  457. + def ssl_wrap_socket(sock, key_file, cert_file,
  458. + cert_reqs=CERT_REQUIRED, ca_certs=None,
  459. + server_hostname=None):
  460. + if not util.safehasattr(socket, 'ssl'):
  461. + raise util.Abort(_('Python SSL support not found'))
  462. + if ca_certs:
  463. + raise util.Abort(_(
  464. + 'certificate checking requires Python 2.6'))
  465.  
  466. - ssl = socket.ssl(sock, key_file, cert_file)
  467. - return httplib.FakeSocket(sock, ssl)
  468. + ssl = socket.ssl(sock, key_file, cert_file)
  469. + return httplib.FakeSocket(sock, ssl)
  470.  
  471. def _verifycert(cert, hostname):
  472. '''Verify that cert (in socket.getpeercert() format) matches hostname.
  473. @@ -115,9 +134,14 @@
  474. self.ui.warn(_("warning: certificate for %s can't be verified "
  475. "(Python too old)\n") % host)
  476. return
  477. + try:
  478. + # work around http://bugs.python.org/issue13721
  479. + if not sock.cipher():
  480. + raise util.Abort(_('%s ssl connection error') % host)
  481. + except AttributeError:
  482. + # This is not the ssl object you are looking for
  483. + pass
  484.  
  485. - if not sock.cipher(): # work around http://bugs.python.org/issue13721
  486. - raise util.Abort(_('%s ssl connection error') % host)
  487. try:
  488. peercert = sock.getpeercert(True)
  489. peercert2 = sock.getpeercert()
  490. diff -r d4a0055af149 -r bd1dc245a27e mercurial/url.py
  491. --- a/mercurial/url.py Fri Aug 23 16:16:22 2013 -0400
  492. +++ b/mercurial/url.py Sun Aug 25 04:06:08 2013 -0600
  493. @@ -181,7 +181,8 @@
  494. self.sock.connect((self.host, self.port))
  495. if _generic_proxytunnel(self):
  496. # we do not support client X.509 certificates
  497. - self.sock = sslutil.ssl_wrap_socket(self.sock, None, None)
  498. + self.sock = sslutil.ssl_wrap_socket(self.sock, None, None,
  499. + server_hostname=self.host)
  500. else:
  501. keepalive.HTTPConnection.connect(self)
  502.  
  503. @@ -338,7 +339,7 @@
  504. _generic_proxytunnel(self)
  505. host = self.realhostport.rsplit(':', 1)[0]
  506. self.sock = sslutil.ssl_wrap_socket(
  507. - self.sock, self.key_file, self.cert_file,
  508. + self.sock, self.key_file, self.cert_file, server_hostname=host,
  509. **sslutil.sslkwargs(self.ui, host))
  510. sslutil.validator(self.ui, host)(self.sock)
Advertisement
Add Comment
Please, Sign In to add comment