Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/sslutil.py @ 28525:dfb21c34e07d
sslutil: allow multiple fingerprints per host
Certificate pinning via [hostfingerprints] is a useful security
feature. Currently, we only support one fingerprint per hostname.
This is simple but it fails in the real world:
* Switching certificates breaks clients until they change the
pinned certificate fingerprint. This incurs client downtime
and can require massive amounts of coordination to perform
certificate changes.
* Some servers operate with multiple certificates on the same
hostname.
This patch adds support for defining multiple certificate
fingerprints per host. This overcomes the deficiencies listed
above. I anticipate the primary use case of this feature will
be to define both the old and new certificate so a certificate
transition can occur with minimal interruption, so this scenario
has been called out in the help documentation.
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Sun, 13 Mar 2016 14:03:58 -0700 |
parents | 6c7d26cef0cd |
children | 7efff6ce9826 |
comparison
equal
deleted
inserted
replaced
28524:ce1160ae2150 | 28525:dfb21c34e07d |
---|---|
160 self.host = host | 160 self.host = host |
161 | 161 |
162 def __call__(self, sock, strict=False): | 162 def __call__(self, sock, strict=False): |
163 host = self.host | 163 host = self.host |
164 cacerts = self.ui.config('web', 'cacerts') | 164 cacerts = self.ui.config('web', 'cacerts') |
165 hostfingerprint = self.ui.config('hostfingerprints', host) | 165 hostfingerprints = self.ui.configlist('hostfingerprints', host) |
166 | 166 |
167 if not sock.cipher(): # work around http://bugs.python.org/issue13721 | 167 if not sock.cipher(): # work around http://bugs.python.org/issue13721 |
168 raise error.Abort(_('%s ssl connection error') % host) | 168 raise error.Abort(_('%s ssl connection error') % host) |
169 try: | 169 try: |
170 peercert = sock.getpeercert(True) | 170 peercert = sock.getpeercert(True) |
176 raise error.Abort(_('%s certificate error: ' | 176 raise error.Abort(_('%s certificate error: ' |
177 'no certificate received') % host) | 177 'no certificate received') % host) |
178 peerfingerprint = util.sha1(peercert).hexdigest() | 178 peerfingerprint = util.sha1(peercert).hexdigest() |
179 nicefingerprint = ":".join([peerfingerprint[x:x + 2] | 179 nicefingerprint = ":".join([peerfingerprint[x:x + 2] |
180 for x in xrange(0, len(peerfingerprint), 2)]) | 180 for x in xrange(0, len(peerfingerprint), 2)]) |
181 if hostfingerprint: | 181 if hostfingerprints: |
182 if peerfingerprint.lower() != \ | 182 fingerprintmatch = False |
183 hostfingerprint.replace(':', '').lower(): | 183 for hostfingerprint in hostfingerprints: |
184 if peerfingerprint.lower() == \ | |
185 hostfingerprint.replace(':', '').lower(): | |
186 fingerprintmatch = True | |
187 break | |
188 if not fingerprintmatch: | |
184 raise error.Abort(_('certificate for %s has unexpected ' | 189 raise error.Abort(_('certificate for %s has unexpected ' |
185 'fingerprint %s') % (host, nicefingerprint), | 190 'fingerprint %s') % (host, nicefingerprint), |
186 hint=_('check hostfingerprint configuration')) | 191 hint=_('check hostfingerprint configuration')) |
187 self.ui.debug('%s certificate matched fingerprint %s\n' % | 192 self.ui.debug('%s certificate matched fingerprint %s\n' % |
188 (host, nicefingerprint)) | 193 (host, nicefingerprint)) |