Show More
@@ -423,6 +423,24 b' Example for ``~/.hgrc``::' | |||||
423 | myfeature = ~/.hgext/myfeature.py |
|
423 | myfeature = ~/.hgext/myfeature.py | |
424 |
|
424 | |||
425 |
|
425 | |||
|
426 | ``hostfingerprints`` | |||
|
427 | """""""""""""""""""" | |||
|
428 | ||||
|
429 | Fingerprints of the certificates of known HTTPS servers. | |||
|
430 | A HTTPS connection to a server with a fingerprint configured here will | |||
|
431 | only succeed if the servers certificate matches the fingerprint. | |||
|
432 | This is very similar to how ssh known hosts works. | |||
|
433 | The fingerprint is the SHA-1 hash value of the DER encoded certificate. | |||
|
434 | The CA chain and web.cacerts is not used for servers with a fingerprint. | |||
|
435 | ||||
|
436 | For example:: | |||
|
437 | ||||
|
438 | [hostfingerprints] | |||
|
439 | hg.intevation.org = 38:76:52:7c:87:26:9a:8f:4a:f8:d3:de:08:45:3b:ea:d6:4b:ee:cc | |||
|
440 | ||||
|
441 | This feature is only supported when using Python 2.6 or later. | |||
|
442 | ||||
|
443 | ||||
426 | ``format`` |
|
444 | ``format`` | |
427 | """""""""" |
|
445 | """""""""" | |
428 |
|
446 |
@@ -546,7 +546,7 b' def remoteui(src, opts):' | |||||
546 | dst.setconfig('bundle', 'mainreporoot', r) |
|
546 | dst.setconfig('bundle', 'mainreporoot', r) | |
547 |
|
547 | |||
548 | # copy selected local settings to the remote ui |
|
548 | # copy selected local settings to the remote ui | |
549 | for sect in ('auth', 'http_proxy'): |
|
549 | for sect in ('auth', 'hostfingerprints', 'http_proxy'): | |
550 | for key, val in src.configitems(sect): |
|
550 | for key, val in src.configitems(sect): | |
551 | dst.setconfig(sect, key, val) |
|
551 | dst.setconfig(sect, key, val) | |
552 | v = src.config('web', 'cacerts') |
|
552 | v = src.config('web', 'cacerts') |
@@ -533,7 +533,8 b' if has_https:' | |||||
533 | else: |
|
533 | else: | |
534 | cacerts = None |
|
534 | cacerts = None | |
535 |
|
535 | |||
536 | if cacerts: |
|
536 | hostfingerprint = self.ui.config('hostfingerprints', self.host) | |
|
537 | if cacerts and not hostfingerprint: | |||
537 | sock = _create_connection((self.host, self.port)) |
|
538 | sock = _create_connection((self.host, self.port)) | |
538 | self.sock = _ssl_wrap_socket(sock, self.key_file, |
|
539 | self.sock = _ssl_wrap_socket(sock, self.key_file, | |
539 | self.cert_file, cert_reqs=CERT_REQUIRED, |
|
540 | self.cert_file, cert_reqs=CERT_REQUIRED, | |
@@ -545,10 +546,33 b' if has_https:' | |||||
545 | self.ui.debug('%s certificate successfully verified\n' % |
|
546 | self.ui.debug('%s certificate successfully verified\n' % | |
546 | self.host) |
|
547 | self.host) | |
547 | else: |
|
548 | else: | |
548 | self.ui.warn(_("warning: %s certificate not verified " |
|
549 | httplib.HTTPSConnection.connect(self) | |
549 | "(check web.cacerts config setting)\n") % |
|
550 | if hasattr(self.sock, 'getpeercert'): | |
|
551 | peercert = self.sock.getpeercert(True) | |||
|
552 | peerfingerprint = util.sha1(peercert).hexdigest() | |||
|
553 | nicefingerprint = ":".join([peerfingerprint[x:x + 2] | |||
|
554 | for x in xrange(0, len(peerfingerprint), 2)]) | |||
|
555 | if hostfingerprint: | |||
|
556 | if peerfingerprint.lower() != \ | |||
|
557 | hostfingerprint.replace(':', '').lower(): | |||
|
558 | raise util.Abort(_('invalid certificate for %s ' | |||
|
559 | 'with fingerprint %s') % | |||
|
560 | (self.host, nicefingerprint)) | |||
|
561 | self.ui.debug('%s certificate matched fingerprint %s\n' % | |||
|
562 | (self.host, nicefingerprint)) | |||
|
563 | else: | |||
|
564 | self.ui.warn(_('warning: %s certificate ' | |||
|
565 | 'with fingerprint %s not verified ' | |||
|
566 | '(check hostfingerprints or web.cacerts ' | |||
|
567 | 'config setting)\n') % | |||
|
568 | (self.host, nicefingerprint)) | |||
|
569 | else: # python 2.5 ? | |||
|
570 | if hostfingerprint: | |||
|
571 | raise util.Abort(_('no certificate for %s ' | |||
|
572 | 'with fingerprint') % self.host) | |||
|
573 | self.ui.warn(_('warning: %s certificate not verified ' | |||
|
574 | '(check web.cacerts config setting)\n') % | |||
550 | self.host) |
|
575 | self.host) | |
551 | httplib.HTTPSConnection.connect(self) |
|
|||
552 |
|
576 | |||
553 | class httpsconnection(BetterHTTPS): |
|
577 | class httpsconnection(BetterHTTPS): | |
554 | response_class = keepalive.HTTPResponse |
|
578 | response_class = keepalive.HTTPResponse |
@@ -106,7 +106,7 b' Test server address cannot be reused' | |||||
106 | clone via pull |
|
106 | clone via pull | |
107 |
|
107 | |||
108 | $ hg clone https://localhost:$HGPORT/ copy-pull |
|
108 | $ hg clone https://localhost:$HGPORT/ copy-pull | |
109 | warning: localhost certificate not verified (check web.cacerts config setting) |
|
109 | warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting) | |
110 | requesting all changes |
|
110 | requesting all changes | |
111 | adding changesets |
|
111 | adding changesets | |
112 | adding manifests |
|
112 | adding manifests | |
@@ -132,7 +132,7 b' pull without cacert' | |||||
132 | $ echo '[hooks]' >> .hg/hgrc |
|
132 | $ echo '[hooks]' >> .hg/hgrc | |
133 | $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc |
|
133 | $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc | |
134 | $ hg pull |
|
134 | $ hg pull | |
135 | warning: localhost certificate not verified (check web.cacerts config setting) |
|
135 | warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting) | |
136 | changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/ |
|
136 | changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/ | |
137 | pulling from https://localhost:$HGPORT/ |
|
137 | pulling from https://localhost:$HGPORT/ | |
138 | searching for changes |
|
138 | searching for changes | |
@@ -188,3 +188,22 b' Test server cert which no longer is vali' | |||||
188 | $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/ |
|
188 | $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/ | |
189 | abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob) |
|
189 | abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob) | |
190 | [255] |
|
190 | [255] | |
|
191 | ||||
|
192 | Fingerprints | |||
|
193 | ||||
|
194 | $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc | |||
|
195 | $ echo "localhost = 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca" >> copy-pull/.hg/hgrc | |||
|
196 | $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc | |||
|
197 | ||||
|
198 | - works without cacerts | |||
|
199 | $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts= | |||
|
200 | 5fed3813f7f5 | |||
|
201 | ||||
|
202 | - fails when cert doesn't match hostname (port is ignored) | |||
|
203 | $ hg -R copy-pull id https://localhost:$HGPORT1/ | |||
|
204 | abort: invalid certificate for localhost with fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b | |||
|
205 | [255] | |||
|
206 | ||||
|
207 | - ignores that certificate doesn't match hostname | |||
|
208 | $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ | |||
|
209 | 5fed3813f7f5 |
General Comments 0
You need to be logged in to leave comments.
Login now