comparison mercurial/sslutil.py @ 29227:dffe78d80a6c

sslutil: convert socket validation from a class to a function (API) Now that the socket validator doesn't have any instance state, we can make it a generic function. The "validator" class has been converted into the "validatesocket" function and all consumers have been updated.
author Gregory Szorc <gregory.szorc@gmail.com>
date Sun, 15 May 2016 11:38:38 -0700
parents 33006bd6a1d7
children 9b07017ba528
comparison
equal deleted inserted replaced
29226:33006bd6a1d7 29227:dffe78d80a6c
289 kws['cert_reqs'] = ssl.CERT_REQUIRED 289 kws['cert_reqs'] = ssl.CERT_REQUIRED
290 return kws 290 return kws
291 291
292 return kws 292 return kws
293 293
294 class validator(object): 294 def validatesocket(sock, strict=False):
295 def __init__(self, ui=None, host=None): 295 """Validate a socket meets security requiremnets.
296 pass 296
297 297 The passed socket must have been created with ``wrapsocket()``.
298 def __call__(self, sock, strict=False): 298 """
299 host = sock._hgstate['hostname'] 299 host = sock._hgstate['hostname']
300 ui = sock._hgstate['ui'] 300 ui = sock._hgstate['ui']
301 301
302 if not sock.cipher(): # work around http://bugs.python.org/issue13721 302 if not sock.cipher(): # work around http://bugs.python.org/issue13721
303 raise error.Abort(_('%s ssl connection error') % host) 303 raise error.Abort(_('%s ssl connection error') % host)
304 try: 304 try:
305 peercert = sock.getpeercert(True) 305 peercert = sock.getpeercert(True)
306 peercert2 = sock.getpeercert() 306 peercert2 = sock.getpeercert()
307 except AttributeError: 307 except AttributeError:
308 raise error.Abort(_('%s ssl connection error') % host) 308 raise error.Abort(_('%s ssl connection error') % host)
309 309
310 if not peercert: 310 if not peercert:
311 raise error.Abort(_('%s certificate error: ' 311 raise error.Abort(_('%s certificate error: '
312 'no certificate received') % host) 312 'no certificate received') % host)
313 313
314 # If a certificate fingerprint is pinned, use it and only it to 314 # If a certificate fingerprint is pinned, use it and only it to
315 # validate the remote cert. 315 # validate the remote cert.
316 hostfingerprints = ui.configlist('hostfingerprints', host) 316 hostfingerprints = ui.configlist('hostfingerprints', host)
317 peerfingerprint = util.sha1(peercert).hexdigest() 317 peerfingerprint = util.sha1(peercert).hexdigest()
318 nicefingerprint = ":".join([peerfingerprint[x:x + 2] 318 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
319 for x in xrange(0, len(peerfingerprint), 2)]) 319 for x in xrange(0, len(peerfingerprint), 2)])
320 if hostfingerprints: 320 if hostfingerprints:
321 fingerprintmatch = False 321 fingerprintmatch = False
322 for hostfingerprint in hostfingerprints: 322 for hostfingerprint in hostfingerprints:
323 if peerfingerprint.lower() == \ 323 if peerfingerprint.lower() == \
324 hostfingerprint.replace(':', '').lower(): 324 hostfingerprint.replace(':', '').lower():
325 fingerprintmatch = True 325 fingerprintmatch = True
326 break 326 break
327 if not fingerprintmatch: 327 if not fingerprintmatch:
328 raise error.Abort(_('certificate for %s has unexpected ' 328 raise error.Abort(_('certificate for %s has unexpected '
329 'fingerprint %s') % (host, nicefingerprint), 329 'fingerprint %s') % (host, nicefingerprint),
330 hint=_('check hostfingerprint configuration')) 330 hint=_('check hostfingerprint configuration'))
331 ui.debug('%s certificate matched fingerprint %s\n' % 331 ui.debug('%s certificate matched fingerprint %s\n' %
332 (host, nicefingerprint)) 332 (host, nicefingerprint))
333 return 333 return
334 334
335 # If insecure connections were explicitly requested via --insecure, 335 # If insecure connections were explicitly requested via --insecure,
336 # print a warning and do no verification. 336 # print a warning and do no verification.
337 # 337 #
338 # It may seem odd that this is checked *after* host fingerprint pinning. 338 # It may seem odd that this is checked *after* host fingerprint pinning.
339 # This is for backwards compatibility (for now). The message is also 339 # This is for backwards compatibility (for now). The message is also
340 # the same as below for BC. 340 # the same as below for BC.
341 if ui.insecureconnections: 341 if ui.insecureconnections:
342 ui.warn(_('warning: %s certificate with fingerprint %s not ' 342 ui.warn(_('warning: %s certificate with fingerprint %s not '
343 'verified (check hostfingerprints or web.cacerts ' 343 'verified (check hostfingerprints or web.cacerts '
344 'config setting)\n') % 344 'config setting)\n') %
345 (host, nicefingerprint))
346 return
347
348 if not sock._hgstate['caloaded']:
349 if strict:
350 raise error.Abort(_('%s certificate with fingerprint %s not '
351 'verified') % (host, nicefingerprint),
352 hint=_('check hostfingerprints or '
353 'web.cacerts config setting'))
354 else:
355 ui.warn(_('warning: %s certificate with fingerprint %s '
356 'not verified (check hostfingerprints or '
357 'web.cacerts config setting)\n') %
345 (host, nicefingerprint)) 358 (host, nicefingerprint))
346 return 359
347 360 return
348 if not sock._hgstate['caloaded']: 361
349 if strict: 362 msg = _verifycert(peercert2, host)
350 raise error.Abort(_('%s certificate with fingerprint %s not ' 363 if msg:
351 'verified') % (host, nicefingerprint), 364 raise error.Abort(_('%s certificate error: %s') % (host, msg),
352 hint=_('check hostfingerprints or ' 365 hint=_('configure hostfingerprint %s or use '
353 'web.cacerts config setting')) 366 '--insecure to connect insecurely') %
354 else: 367 nicefingerprint)
355 ui.warn(_('warning: %s certificate with fingerprint %s '
356 'not verified (check hostfingerprints or '
357 'web.cacerts config setting)\n') %
358 (host, nicefingerprint))
359
360 return
361
362 msg = _verifycert(peercert2, host)
363 if msg:
364 raise error.Abort(_('%s certificate error: %s') % (host, msg),
365 hint=_('configure hostfingerprint %s or use '
366 '--insecure to connect insecurely') %
367 nicefingerprint)