url: SSL server certificate verification using web.cacerts file (
issue1174)
--- a/doc/hgrc.5.txt Wed Feb 10 20:08:18 2010 +0100
+++ b/doc/hgrc.5.txt Wed Feb 10 20:27:46 2010 +0100
@@ -873,6 +873,26 @@
Base URL to use when publishing URLs in other locations, so
third-party tools like email notification hooks can construct
URLs. Example: ``http://hgserver/repos/``.
+``cacerts``
+ Path to file containing a list of PEM encoded certificate authorities
+ that may be used to verify an SSL server's identity. The form must be
+ as follows::
+
+ -----BEGIN CERTIFICATE-----
+ ... (certificate in base64 PEM encoding) ...
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ ... (certificate in base64 PEM encoding) ...
+ -----END CERTIFICATE-----
+
+ This feature is only supported when using Python 2.6. If you wish to
+ use it with earlier versions of Python, install the backported
+ version of the ssl library that is available from
+ ``http://pypi.python.org``.
+
+ You can use OpenSSL's CA certificate file if your platform has one.
+ On most Linux systems this will be ``/etc/ssl/certs/ca-certificates.crt``.
+ Otherwise you will have to generate this file manually.
``contact``
Name or email address of the person in charge of the repository.
Defaults to ui.username or ``$EMAIL`` or "unknown" if unset or empty.
--- a/mercurial/url.py Wed Feb 10 20:08:18 2010 +0100
+++ b/mercurial/url.py Wed Feb 10 20:27:46 2010 +0100
@@ -255,11 +255,48 @@
# avoid using deprecated/broken FakeSocket in python 2.6
import ssl
_ssl_wrap_socket = ssl.wrap_socket
+ CERT_REQUIRED = ssl.CERT_REQUIRED
except ImportError:
- def _ssl_wrap_socket(sock, key_file, cert_file):
+ CERT_REQUIRED = 2
+
+ def _ssl_wrap_socket(sock, key_file, cert_file,
+ cert_reqs=CERT_REQUIRED, ca_certs=None):
+ if ca_certs:
+ raise util.Abort(_(
+ 'certificate checking requires Python 2.6'))
+
ssl = socket.ssl(sock, key_file, cert_file)
return httplib.FakeSocket(sock, ssl)
+ _GLOBAL_DEFAULT_TIMEOUT = object()
+
+ try:
+ _create_connection = socket.create_connection
+ except ImportError:
+ def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
+ source_address=None):
+ # lifted from Python 2.6
+
+ msg = "getaddrinfo returns an empty list"
+ host, port = address
+ for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
+ af, socktype, proto, canonname, sa = res
+ sock = None
+ try:
+ sock = socket.socket(af, socktype, proto)
+ if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
+ sock.settimeout(timeout)
+ if source_address:
+ sock.bind(source_address)
+ sock.connect(sa)
+ return sock
+
+ except socket.error, msg:
+ if sock is not None:
+ sock.close()
+
+ raise socket.error, msg
+
class httpconnection(keepalive.HTTPConnection):
# must be able to send big bundle as stream.
send = _gen_sendfile(keepalive.HTTPConnection)
@@ -427,6 +464,21 @@
class BetterHTTPS(httplib.HTTPSConnection):
send = keepalive.safesend
+ def connect(self):
+ if hasattr(self, 'ui'):
+ cacerts = self.ui.config('web', 'cacerts')
+ else:
+ cacerts = None
+
+ if cacerts:
+ sock = _create_connection((self.host, self.port))
+ self.sock = _ssl_wrap_socket(sock, self.key_file,
+ self.cert_file, cert_reqs=CERT_REQUIRED,
+ ca_certs=cacerts)
+ self.ui.debug(_('server identity verification succeeded\n'))
+ else:
+ httplib.HTTPSConnection.connect(self)
+
class httpsconnection(BetterHTTPS):
response_class = keepalive.HTTPResponse
# must be able to send big bundle as stream.
@@ -473,7 +525,9 @@
keyfile = self.auth['key']
certfile = self.auth['cert']
- return httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
+ conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
+ conn.ui = self.ui
+ return conn
# In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
# it doesn't know about the auth type requested. This can happen if