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) |
|