Mercurial > public > mercurial-scm > hg
comparison mercurial/sslutil.py @ 36745:424994a0adfd
sslutil: lots of unicode/bytes cleanup
In general, we handle hostnames as bytes, except where Python forces
them to be unicodes.
This fixes all the tracebacks I was seeing in test-https.t, but
there's still some ECONNRESET weirdness that I can't hunt down...
Differential Revision: https://phab.mercurial-scm.org/D2687
author | Augie Fackler <augie@google.com> |
---|---|
date | Sun, 04 Mar 2018 18:03:55 -0500 |
parents | 72b91f905065 |
children | 25798cf7dc9d |
comparison
equal
deleted
inserted
replaced
36741:7a25f6cfebe8 | 36745:424994a0adfd |
---|---|
111 def _hostsettings(ui, hostname): | 111 def _hostsettings(ui, hostname): |
112 """Obtain security settings for a hostname. | 112 """Obtain security settings for a hostname. |
113 | 113 |
114 Returns a dict of settings relevant to that hostname. | 114 Returns a dict of settings relevant to that hostname. |
115 """ | 115 """ |
116 bhostname = pycompat.bytesurl(hostname) | |
116 s = { | 117 s = { |
117 # Whether we should attempt to load default/available CA certs | 118 # Whether we should attempt to load default/available CA certs |
118 # if an explicit ``cafile`` is not defined. | 119 # if an explicit ``cafile`` is not defined. |
119 'allowloaddefaultcerts': True, | 120 'allowloaddefaultcerts': True, |
120 # List of 2-tuple of (hash algorithm, hash). | 121 # List of 2-tuple of (hash algorithm, hash). |
160 # internal config: hostsecurity.disabletls10warning | 161 # internal config: hostsecurity.disabletls10warning |
161 if not ui.configbool('hostsecurity', 'disabletls10warning'): | 162 if not ui.configbool('hostsecurity', 'disabletls10warning'): |
162 ui.warn(_('warning: connecting to %s using legacy security ' | 163 ui.warn(_('warning: connecting to %s using legacy security ' |
163 'technology (TLS 1.0); see ' | 164 'technology (TLS 1.0); see ' |
164 'https://mercurial-scm.org/wiki/SecureConnections for ' | 165 'https://mercurial-scm.org/wiki/SecureConnections for ' |
165 'more info\n') % hostname) | 166 'more info\n') % bhostname) |
166 defaultprotocol = 'tls1.0' | 167 defaultprotocol = 'tls1.0' |
167 | 168 |
168 key = 'minimumprotocol' | 169 key = 'minimumprotocol' |
169 protocol = ui.config('hostsecurity', key, defaultprotocol) | 170 protocol = ui.config('hostsecurity', key, defaultprotocol) |
170 validateprotocol(protocol, key) | 171 validateprotocol(protocol, key) |
171 | 172 |
172 key = '%s:minimumprotocol' % hostname | 173 key = '%s:minimumprotocol' % bhostname |
173 protocol = ui.config('hostsecurity', key, protocol) | 174 protocol = ui.config('hostsecurity', key, protocol) |
174 validateprotocol(protocol, key) | 175 validateprotocol(protocol, key) |
175 | 176 |
176 # If --insecure is used, we allow the use of TLS 1.0 despite config options. | 177 # If --insecure is used, we allow the use of TLS 1.0 despite config options. |
177 # We always print a "connection security to %s is disabled..." message when | 178 # We always print a "connection security to %s is disabled..." message when |
180 protocol = 'tls1.0' | 181 protocol = 'tls1.0' |
181 | 182 |
182 s['protocol'], s['ctxoptions'], s['protocolui'] = protocolsettings(protocol) | 183 s['protocol'], s['ctxoptions'], s['protocolui'] = protocolsettings(protocol) |
183 | 184 |
184 ciphers = ui.config('hostsecurity', 'ciphers') | 185 ciphers = ui.config('hostsecurity', 'ciphers') |
185 ciphers = ui.config('hostsecurity', '%s:ciphers' % hostname, ciphers) | 186 ciphers = ui.config('hostsecurity', '%s:ciphers' % bhostname, ciphers) |
186 s['ciphers'] = ciphers | 187 s['ciphers'] = ciphers |
187 | 188 |
188 # Look for fingerprints in [hostsecurity] section. Value is a list | 189 # Look for fingerprints in [hostsecurity] section. Value is a list |
189 # of <alg>:<fingerprint> strings. | 190 # of <alg>:<fingerprint> strings. |
190 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname) | 191 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % bhostname) |
191 for fingerprint in fingerprints: | 192 for fingerprint in fingerprints: |
192 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))): | 193 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))): |
193 raise error.Abort(_('invalid fingerprint for %s: %s') % ( | 194 raise error.Abort(_('invalid fingerprint for %s: %s') % ( |
194 hostname, fingerprint), | 195 bhostname, fingerprint), |
195 hint=_('must begin with "sha1:", "sha256:", ' | 196 hint=_('must begin with "sha1:", "sha256:", ' |
196 'or "sha512:"')) | 197 'or "sha512:"')) |
197 | 198 |
198 alg, fingerprint = fingerprint.split(':', 1) | 199 alg, fingerprint = fingerprint.split(':', 1) |
199 fingerprint = fingerprint.replace(':', '').lower() | 200 fingerprint = fingerprint.replace(':', '').lower() |
200 s['certfingerprints'].append((alg, fingerprint)) | 201 s['certfingerprints'].append((alg, fingerprint)) |
201 | 202 |
202 # Fingerprints from [hostfingerprints] are always SHA-1. | 203 # Fingerprints from [hostfingerprints] are always SHA-1. |
203 for fingerprint in ui.configlist('hostfingerprints', hostname): | 204 for fingerprint in ui.configlist('hostfingerprints', bhostname): |
204 fingerprint = fingerprint.replace(':', '').lower() | 205 fingerprint = fingerprint.replace(':', '').lower() |
205 s['certfingerprints'].append(('sha1', fingerprint)) | 206 s['certfingerprints'].append(('sha1', fingerprint)) |
206 s['legacyfingerprint'] = True | 207 s['legacyfingerprint'] = True |
207 | 208 |
208 # If a host cert fingerprint is defined, it is the only thing that | 209 # If a host cert fingerprint is defined, it is the only thing that |
221 s['allowloaddefaultcerts'] = False | 222 s['allowloaddefaultcerts'] = False |
222 | 223 |
223 # If both fingerprints and a per-host ca file are specified, issue a warning | 224 # If both fingerprints and a per-host ca file are specified, issue a warning |
224 # because users should not be surprised about what security is or isn't | 225 # because users should not be surprised about what security is or isn't |
225 # being performed. | 226 # being performed. |
226 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname) | 227 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % bhostname) |
227 if s['certfingerprints'] and cafile: | 228 if s['certfingerprints'] and cafile: |
228 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host ' | 229 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host ' |
229 'fingerprints defined; using host fingerprints for ' | 230 'fingerprints defined; using host fingerprints for ' |
230 'verification)\n') % hostname) | 231 'verification)\n') % bhostname) |
231 | 232 |
232 # Try to hook up CA certificate validation unless something above | 233 # Try to hook up CA certificate validation unless something above |
233 # makes it not necessary. | 234 # makes it not necessary. |
234 if s['verifymode'] is None: | 235 if s['verifymode'] is None: |
235 # Look at per-host ca file first. | 236 # Look at per-host ca file first. |
236 if cafile: | 237 if cafile: |
237 cafile = util.expandpath(cafile) | 238 cafile = util.expandpath(cafile) |
238 if not os.path.exists(cafile): | 239 if not os.path.exists(cafile): |
239 raise error.Abort(_('path specified by %s does not exist: %s') % | 240 raise error.Abort(_('path specified by %s does not exist: %s') % |
240 ('hostsecurity.%s:verifycertsfile' % hostname, | 241 ('hostsecurity.%s:verifycertsfile' % ( |
241 cafile)) | 242 bhostname,), cafile)) |
242 s['cafile'] = cafile | 243 s['cafile'] = cafile |
243 else: | 244 else: |
244 # Find global certificates file in config. | 245 # Find global certificates file in config. |
245 cafile = ui.config('web', 'cacerts') | 246 cafile = ui.config('web', 'cacerts') |
246 | 247 |
388 if len(e.args) == 1: # pypy has different SSLError args | 389 if len(e.args) == 1: # pypy has different SSLError args |
389 msg = e.args[0] | 390 msg = e.args[0] |
390 else: | 391 else: |
391 msg = e.args[1] | 392 msg = e.args[1] |
392 raise error.Abort(_('error loading CA file %s: %s') % ( | 393 raise error.Abort(_('error loading CA file %s: %s') % ( |
393 settings['cafile'], msg), | 394 settings['cafile'], util.forcebytestr(msg)), |
394 hint=_('file is empty or malformed?')) | 395 hint=_('file is empty or malformed?')) |
395 caloaded = True | 396 caloaded = True |
396 elif settings['allowloaddefaultcerts']: | 397 elif settings['allowloaddefaultcerts']: |
397 # This is a no-op on old Python. | 398 # This is a no-op on old Python. |
398 sslcontext.load_default_certs() | 399 sslcontext.load_default_certs() |
581 the value in ``dn``. | 582 the value in ``dn``. |
582 """ | 583 """ |
583 pats = [] | 584 pats = [] |
584 if not dn: | 585 if not dn: |
585 return False | 586 return False |
586 | 587 dn = pycompat.bytesurl(dn) |
587 pieces = dn.split(r'.') | 588 hostname = pycompat.bytesurl(hostname) |
589 | |
590 pieces = dn.split('.') | |
588 leftmost = pieces[0] | 591 leftmost = pieces[0] |
589 remainder = pieces[1:] | 592 remainder = pieces[1:] |
590 wildcards = leftmost.count('*') | 593 wildcards = leftmost.count('*') |
591 if wildcards > maxwildcards: | 594 if wildcards > maxwildcards: |
592 raise wildcarderror( | 595 raise wildcarderror( |
635 if key == 'DNS': | 638 if key == 'DNS': |
636 try: | 639 try: |
637 if _dnsnamematch(value, hostname): | 640 if _dnsnamematch(value, hostname): |
638 return | 641 return |
639 except wildcarderror as e: | 642 except wildcarderror as e: |
640 return e.args[0] | 643 return util.forcebytestr(e.args[0]) |
641 | 644 |
642 dnsnames.append(value) | 645 dnsnames.append(value) |
643 | 646 |
644 if not dnsnames: | 647 if not dnsnames: |
645 # The subject is only checked when there is no DNS in subjectAltName. | 648 # The subject is only checked when there is no DNS in subjectAltName. |
646 for sub in cert.get('subject', []): | 649 for sub in cert.get(r'subject', []): |
647 for key, value in sub: | 650 for key, value in sub: |
648 # According to RFC 2818 the most specific Common Name must | 651 # According to RFC 2818 the most specific Common Name must |
649 # be used. | 652 # be used. |
650 if key == 'commonName': | 653 if key == r'commonName': |
651 # 'subject' entries are unicode. | 654 # 'subject' entries are unicode. |
652 try: | 655 try: |
653 value = value.encode('ascii') | 656 value = value.encode('ascii') |
654 except UnicodeEncodeError: | 657 except UnicodeEncodeError: |
655 return _('IDN in certificate not supported') | 658 return _('IDN in certificate not supported') |
656 | 659 |
657 try: | 660 try: |
658 if _dnsnamematch(value, hostname): | 661 if _dnsnamematch(value, hostname): |
659 return | 662 return |
660 except wildcarderror as e: | 663 except wildcarderror as e: |
661 return e.args[0] | 664 return util.forcebytestr(e.args[0]) |
662 | 665 |
663 dnsnames.append(value) | 666 dnsnames.append(value) |
664 | 667 |
665 if len(dnsnames) > 1: | 668 if len(dnsnames) > 1: |
666 return _('certificate is for %s') % ', '.join(dnsnames) | 669 return _('certificate is for %s') % ', '.join(dnsnames) |
778 def validatesocket(sock): | 781 def validatesocket(sock): |
779 """Validate a socket meets security requirements. | 782 """Validate a socket meets security requirements. |
780 | 783 |
781 The passed socket must have been created with ``wrapsocket()``. | 784 The passed socket must have been created with ``wrapsocket()``. |
782 """ | 785 """ |
783 host = sock._hgstate['hostname'] | 786 shost = sock._hgstate['hostname'] |
787 host = pycompat.bytesurl(shost) | |
784 ui = sock._hgstate['ui'] | 788 ui = sock._hgstate['ui'] |
785 settings = sock._hgstate['settings'] | 789 settings = sock._hgstate['settings'] |
786 | 790 |
787 try: | 791 try: |
788 peercert = sock.getpeercert(True) | 792 peercert = sock.getpeercert(True) |
854 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for ' | 858 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for ' |
855 'how to configure Mercurial to avoid this error or set ' | 859 'how to configure Mercurial to avoid this error or set ' |
856 'hostsecurity.%s:fingerprints=%s to trust this server') % | 860 'hostsecurity.%s:fingerprints=%s to trust this server') % |
857 (host, nicefingerprint)) | 861 (host, nicefingerprint)) |
858 | 862 |
859 msg = _verifycert(peercert2, host) | 863 msg = _verifycert(peercert2, shost) |
860 if msg: | 864 if msg: |
861 raise error.Abort(_('%s certificate error: %s') % (host, msg), | 865 raise error.Abort(_('%s certificate error: %s') % (host, msg), |
862 hint=_('set hostsecurity.%s:certfingerprints=%s ' | 866 hint=_('set hostsecurity.%s:certfingerprints=%s ' |
863 'config setting or use --insecure to connect ' | 867 'config setting or use --insecure to connect ' |
864 'insecurely') % | 868 'insecurely') % |