Mercurial > hg
comparison mercurial/sslutil.py @ 29258:6315c1e14f75
sslutil: introduce a function for determining host-specific settings
This patch marks the beginning of a series that introduces a new,
more configurable, per-host security settings mechanism. Currently,
we have global settings (like web.cacerts and the --insecure argument).
We also have per-host settings via [hostfingerprints].
Global security settings are good for defaults, but they don't
provide the amount of control often wanted. For example, an
organization may want to require a particular CA is used for a
particular hostname.
[hostfingerprints] is nice. But it currently assumes SHA-1.
Furthermore, there is no obvious place to put additional per-host
settings.
Subsequent patches will be introducing new mechanisms for defining
security settings, some on a per-host basis. This commits starts
the transition to that world by introducing the _hostsettings
function. It takes a ui and hostname and returns a dict of security
settings. Currently, it limits itself to returning host fingerprint
info.
We foreshadow the future support of non-SHA1 hashing algorithms
for verifying the host fingerprint by making the "certfingerprints"
key a list of tuples instead of a list of hashes.
We add this dict to the hgstate property on the socket and use it
during socket validation for checking fingerprints. There should be
no change in behavior.
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Sat, 28 May 2016 11:12:02 -0700 |
parents | 9da137faaa9c |
children | ec247e8595f9 |
comparison
equal
deleted
inserted
replaced
29253:9da137faaa9c | 29258:6315c1e14f75 |
---|---|
103 | 103 |
104 if self._supportsciphers: | 104 if self._supportsciphers: |
105 args['ciphers'] = self._ciphers | 105 args['ciphers'] = self._ciphers |
106 | 106 |
107 return ssl.wrap_socket(socket, **args) | 107 return ssl.wrap_socket(socket, **args) |
108 | |
109 def _hostsettings(ui, hostname): | |
110 """Obtain security settings for a hostname. | |
111 | |
112 Returns a dict of settings relevant to that hostname. | |
113 """ | |
114 s = { | |
115 # List of 2-tuple of (hash algorithm, hash). | |
116 'certfingerprints': [], | |
117 } | |
118 | |
119 # Fingerprints from [hostfingerprints] are always SHA-1. | |
120 for fingerprint in ui.configlist('hostfingerprints', hostname, []): | |
121 fingerprint = fingerprint.replace(':', '').lower() | |
122 s['certfingerprints'].append(('sha1', fingerprint)) | |
123 | |
124 return s | |
108 | 125 |
109 def _determinecertoptions(ui, host): | 126 def _determinecertoptions(ui, host): |
110 """Determine certificate options for a connections. | 127 """Determine certificate options for a connections. |
111 | 128 |
112 Returns a tuple of (cert_reqs, ca_certs). | 129 Returns a tuple of (cert_reqs, ca_certs). |
215 raise error.Abort(_('ssl connection failed')) | 232 raise error.Abort(_('ssl connection failed')) |
216 | 233 |
217 sslsocket._hgstate = { | 234 sslsocket._hgstate = { |
218 'caloaded': caloaded, | 235 'caloaded': caloaded, |
219 'hostname': serverhostname, | 236 'hostname': serverhostname, |
237 'settings': _hostsettings(ui, serverhostname), | |
220 'ui': ui, | 238 'ui': ui, |
221 } | 239 } |
222 | 240 |
223 return sslsocket | 241 return sslsocket |
224 | 242 |
290 | 308 |
291 The passed socket must have been created with ``wrapsocket()``. | 309 The passed socket must have been created with ``wrapsocket()``. |
292 """ | 310 """ |
293 host = sock._hgstate['hostname'] | 311 host = sock._hgstate['hostname'] |
294 ui = sock._hgstate['ui'] | 312 ui = sock._hgstate['ui'] |
313 settings = sock._hgstate['settings'] | |
295 | 314 |
296 try: | 315 try: |
297 peercert = sock.getpeercert(True) | 316 peercert = sock.getpeercert(True) |
298 peercert2 = sock.getpeercert() | 317 peercert2 = sock.getpeercert() |
299 except AttributeError: | 318 except AttributeError: |
303 raise error.Abort(_('%s certificate error: ' | 322 raise error.Abort(_('%s certificate error: ' |
304 'no certificate received') % host) | 323 'no certificate received') % host) |
305 | 324 |
306 # If a certificate fingerprint is pinned, use it and only it to | 325 # If a certificate fingerprint is pinned, use it and only it to |
307 # validate the remote cert. | 326 # validate the remote cert. |
308 hostfingerprints = ui.configlist('hostfingerprints', host) | |
309 peerfingerprint = util.sha1(peercert).hexdigest() | 327 peerfingerprint = util.sha1(peercert).hexdigest() |
310 nicefingerprint = ":".join([peerfingerprint[x:x + 2] | 328 nicefingerprint = ":".join([peerfingerprint[x:x + 2] |
311 for x in xrange(0, len(peerfingerprint), 2)]) | 329 for x in xrange(0, len(peerfingerprint), 2)]) |
312 if hostfingerprints: | 330 if settings['certfingerprints']: |
313 fingerprintmatch = False | 331 fingerprintmatch = False |
314 for hostfingerprint in hostfingerprints: | 332 for hash, fingerprint in settings['certfingerprints']: |
315 if peerfingerprint.lower() == \ | 333 if peerfingerprint.lower() == fingerprint: |
316 hostfingerprint.replace(':', '').lower(): | |
317 fingerprintmatch = True | 334 fingerprintmatch = True |
318 break | 335 break |
319 if not fingerprintmatch: | 336 if not fingerprintmatch: |
320 raise error.Abort(_('certificate for %s has unexpected ' | 337 raise error.Abort(_('certificate for %s has unexpected ' |
321 'fingerprint %s') % (host, nicefingerprint), | 338 'fingerprint %s') % (host, nicefingerprint), |