Merge lp:~jelmer/bzr/urllib-verifies-ssl-certs into lp:bzr

Proposed by Jelmer Vernooij
Status: Superseded
Proposed branch: lp:~jelmer/bzr/urllib-verifies-ssl-certs
Merge into: lp:bzr
Diff against target: 460 lines (+305/-18) (has conflicts)
9 files modified
bzrlib/config.py (+20/-0)
bzrlib/errors.py (+8/-0)
bzrlib/tests/__init__.py (+1/-0)
bzrlib/tests/test_http.py (+0/-1)
bzrlib/tests/test_https_urllib.py (+109/-0)
bzrlib/transport/__init__.py (+3/-3)
bzrlib/transport/http/_urllib2_wrappers.py (+141/-14)
doc/en/release-notes/bzr-2.5.txt (+6/-0)
doc/en/whats-new/whats-new-in-2.5.txt (+17/-0)
Text conflict in bzrlib/config.py
To merge this branch: bzr merge lp:~jelmer/bzr/urllib-verifies-ssl-certs
Reviewer Review Type Date Requested Status
Martin Packman (community) Needs Fixing
Vincent Ladeuil Pending
Review via email: mp+86360@code.launchpad.net

This proposal supersedes a proposal from 2011-11-04.

This proposal has been superseded by a proposal from 2012-01-03.

Description of the change

Add support for verifying SSL certificates when using urllib.

To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote : Posted in a previous version of this proposal

On 4 November 2011 14:03, Jelmer Vernooij <email address hidden> wrote:
> Jelmer Vernooij has proposed merging lp:~jelmer/bzr/urllib-verifies-ssl-certs into lp:bzr.
>
> Requested reviews:
>  bzr-core (bzr-core)
> Related bugs:
>  Bug #125055 in Bazaar: "defaulting to pycurl doesn't make sense anymore"
>  https://bugs.launchpad.net/bzr/+bug/125055
>  Bug #651161 in Bazaar: "bzr fails to verify ssl validity in https connections - by default --> as pycurl isn't a dep only a suggestion "
>  https://bugs.launchpad.net/bzr/+bug/651161
>
> For more details, see:
> https://code.launchpad.net/~jelmer/bzr/urllib-verifies-ssl-certs/+merge/81227
>
> Add support for verifying SSL certificates when using urllib.

That's great, thanks.

> This branch does actually work, but it's got some open ends:
>
> * The location with the CAs to trust differs per system. Rather than hardcoding
>  a list, I think we should have a configuration option that allows the user
>  to specify what CA list they want to verify against.
>
>  This option could then also be pointed at a user-specific list
>  (rather than a system wide list). And packagers can put in the default
>  for their distribution.
>
>  Where do I obtain this option from, which config stack ?

[needsfixing] I think the global stack would be a good place to start
- it would seem like a kind of contrived case to want different values
on a smaller scope. We can always change it later.

> * We probably want some way to specify that self-signed certificates are ok.
>  Probably also a new option? We should probably still warn the user
>  when a self-signed certificate is being trusted.

+1 to a new option and to always warning.

Though perhaps it would be equivalent and easier to just have an
option saying "don't do validation."

> * There are no additional tests. There probably should be some, at least for
>  e.g. the match_hostname helper.

It would be nice, if it's not too hard, to actually test against an
untrusted cert, or at least to do monkeypatching to make it look like
we got an invalid cert.

[needsfixing] I'd like a unit test for match_hostname covering the
various cases.

> * I have the feeling that I'm missing something. This was way too easy.
>  What is it?

I haven't read the rfc but it looks plausible. You could wait for a
review from vila, who has probably touched this most, or robert who is
an http nerd.

I wonder if this needs a change to Windows or Mac packaging to make
sure the certs are there. Or, possibly we can get them from the
system's keyring in some way.

Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal
Download full text (4.0 KiB)

> Add support for verifying SSL certificates when using urllib.

\o/

>
> This branch does actually work, but it's got some open ends:
>
> * The location with the CAs to trust differs per system. Rather than hardcoding
> a list, I think we should have a configuration option that allows the user
> to specify what CA list they want to verify against.

pycurl already handles that for windows but getting it right in the general
case... sounds like a packaging issue :-/

>
> This option could then also be pointed at a user-specific list
> (rather than a system wide list). And packagers can put in the default
> for their distribution.
>
> Where do I obtain this option from, which config stack ?

I'd go with global to start with specifying the default value at
registration time if possible.

Arguably it's a system-wide config file that should be used, making packager
life easier.

>
> * We probably want some way to specify that self-signed certificates are ok.
> Probably also a new option? We should probably still warn the user
> when a self-signed certificate is being trusted.

authentication.conf is the right place for that and the option is even
documented but not implemented ;)

>
> * There are no additional tests. There probably should be some, at least for
> e.g. the match_hostname helper.

Yup. There are already https tests for pycurl that should be activated now
(<broken record> hey, did I mention the 'scenarios' attribute is cool for
that ? </broken record> ;)

>
> * I have the feeling that I'm missing something. This was way too easy.
> What is it?

Hehe.

The last time I worked on this area we were still supporting python-2.4/2.5
where https wasn't supported. Now, you also found the relevant code in
python-3.2 so... that certainly simplify things *a lot* :)

This wasn't a full review but as I understand it you weren't requesting one
either ;)

As for tests, if you look at the pycurl ones you'll certainly find that most
of the needed infrastructure is there to build certificates for special
purposes (self-signed, obsolete, etc) and easily setup an https server.

99 + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
100 + are mostly followed, but IP addresses are not accepted for *hostname*.

Hmm, IP addresses not accepted ? I bet our tests won't like that much.

pycurl itself was not open enough to test for these weird certificates AFAIR
but it's also a robust implementation so I wasn't concerned, but if we make
urllib the default one, we have to make sure we're making the right checks.

I'll need to dig a bit more to find back the relevant discussions about this
topic.

Also, defaulting to urllib is fine but the documentation probably needs to
be updated (created even ?) to make it clear that we're not defaulting to
pycurl. We had issues in the past (including failures to check lp
certificate) which had led to the actual defaults (urllib for http, pycurl
for https), let's make the switch crystal clear.

141 def connect_to_origin(self):
142 - ssl_sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)
143 + ssl_sock = ssl.wrap_socket(self.sock, self.key_file, self.cert_file,
144 + ...

Read more...

Revision history for this message
Martin Packman (gz) wrote : Posted in a previous version of this proposal

I think there's general agreement this is great, and with the reqirement of Python 2.6 something that's now acheivable and should probably get into the next bzr release.

It absolutely needs some kind of automated testing though, and likely an option for disabling checks, I've used `curl -k` and `wget --no-check-certificate` enough to know I'd be annoyed if that option wasn't there.

review: Needs Fixing
Revision history for this message
Martin Packman (gz) wrote :

Summarising from earlier rambling chat:

* The error shown when a certificate can't be checked because no local store is found needs to spell out the `-Ossl.cert_reqs=none` parameter for getting the old insecure behaviour, and maybe a pointer to more help.
* Perhaps the error shown when a self-signed certificate doesn't validate against the local store also wants some love.
* An entry in doc/en/whats-new/whats-new-in-2.5.txt needs adding spelling out the user-facing aspects of this change, as it's a nice improvement but some people may need to change their configuration on upgrading.

The mercurial wiki page linked earlier is a useful comparison:
<http://mercurial.selenic.com/wiki/CACertificates>

review: Needs Fixing
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

> Summarising from earlier rambling chat:
>
> * The error shown when a certificate can't be checked because no local store
> is found needs to spell out the `-Ossl.cert_reqs=none` parameter for getting
> the old insecure behaviour, and maybe a pointer to more help.
> * Perhaps the error shown when a self-signed certificate doesn't validate
> against the local store also wants some love.
> * An entry in doc/en/whats-new/whats-new-in-2.5.txt needs adding spelling out
> the user-facing aspects of this change, as it's a nice improvement but some
> people may need to change their configuration on upgrading.

Done.

Ironically I can't actually run "bzr selftest -s bt.test_http" on my machine because pycurl triggers segfaults (not just on this branch), but the changes are mostly documentation anyway.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/config.py'
--- bzrlib/config.py 2012-01-03 12:56:06 +0000
+++ bzrlib/config.py 2012-01-03 16:18:27 +0000
@@ -1652,6 +1652,19 @@
1652 raise errors.NoWhoami()1652 raise errors.NoWhoami()
16531653
16541654
1655<<<<<<< TREE
1656=======
1657def email_from_store(unicode_str):
1658 """Unlike other env vars, BZR_EMAIL takes precedence over config settings.
1659
1660 Whatever comes from a config file is then overridden.
1661 """
1662 value = os.environ.get('BZR_EMAIL')
1663 if value:
1664 return value.decode(osutils.get_user_encoding())
1665 return unicode_str
1666
1667>>>>>>> MERGE-SOURCE
1655def _auto_user_id():1668def _auto_user_id():
1656 """Calculate automatic user identification.1669 """Calculate automatic user identification.
16571670
@@ -2858,6 +2871,13 @@
2858by the ``submit:`` revision spec.2871by the ``submit:`` revision spec.
2859"""))2872"""))
28602873
2874option_registry.register_lazy('ssl.ca_certs',
2875 'bzrlib.transport.http._urllib2_wrappers', 'opt_ssl_ca_certs')
2876
2877option_registry.register_lazy('ssl.cert_reqs',
2878 'bzrlib.transport.http._urllib2_wrappers', 'opt_ssl_cert_reqs')
2879
2880
28612881
2862class Section(object):2882class Section(object):
2863 """A section defines a dict of option name => value.2883 """A section defines a dict of option name => value.
28642884
=== modified file 'bzrlib/errors.py'
--- bzrlib/errors.py 2011-12-22 15:33:16 +0000
+++ bzrlib/errors.py 2012-01-03 16:18:27 +0000
@@ -1667,6 +1667,14 @@
1667 TransportError.__init__(self, msg, orig_error=orig_error)1667 TransportError.__init__(self, msg, orig_error=orig_error)
16681668
16691669
1670class CertificateError(TransportError):
1671
1672 _fmt = "Certificate error: %(error)s"
1673
1674 def __init__(self, error):
1675 self.error = error
1676
1677
1670class InvalidHttpRange(InvalidHttpResponse):1678class InvalidHttpRange(InvalidHttpResponse):
16711679
1672 _fmt = "Invalid http range %(range)r for %(path)s: %(msg)s"1680 _fmt = "Invalid http range %(range)r for %(path)s: %(msg)s"
16731681
=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2011-12-24 10:10:59 +0000
+++ bzrlib/tests/__init__.py 2012-01-03 16:18:27 +0000
@@ -4002,6 +4002,7 @@
4002 'bzrlib.tests.test_http',4002 'bzrlib.tests.test_http',
4003 'bzrlib.tests.test_http_response',4003 'bzrlib.tests.test_http_response',
4004 'bzrlib.tests.test_https_ca_bundle',4004 'bzrlib.tests.test_https_ca_bundle',
4005 'bzrlib.tests.test_https_urllib',
4005 'bzrlib.tests.test_i18n',4006 'bzrlib.tests.test_i18n',
4006 'bzrlib.tests.test_identitymap',4007 'bzrlib.tests.test_identitymap',
4007 'bzrlib.tests.test_ignores',4008 'bzrlib.tests.test_ignores',
40084009
=== modified file 'bzrlib/tests/test_http.py'
--- bzrlib/tests/test_http.py 2011-10-12 16:00:13 +0000
+++ bzrlib/tests/test_http.py 2012-01-03 16:18:27 +0000
@@ -32,7 +32,6 @@
32import bzrlib32import bzrlib
33from bzrlib import (33from bzrlib import (
34 bzrdir,34 bzrdir,
35 cethread,
36 config,35 config,
37 debug,36 debug,
38 errors,37 errors,
3938
=== added file 'bzrlib/tests/test_https_urllib.py'
--- bzrlib/tests/test_https_urllib.py 1970-01-01 00:00:00 +0000
+++ bzrlib/tests/test_https_urllib.py 2012-01-03 16:18:27 +0000
@@ -0,0 +1,109 @@
1# Copyright (C) 2011 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17"""Tests for the SSL support in the urllib HTTP transport.
18
19"""
20
21import ssl
22
23from bzrlib import trace
24from bzrlib.errors import (
25 CertificateError,
26 ConfigOptionValueError,
27 )
28from bzrlib.config import (
29 IniFileStore,
30 Stack,
31 )
32import os
33from bzrlib.tests import (
34 TestCase,
35 TestCaseInTempDir,
36 )
37from bzrlib.transport.http import _urllib2_wrappers
38
39
40def stack_from_string(text):
41 store = IniFileStore()
42 store._load_from_string(text)
43 return Stack([store.get_sections])
44
45
46class CaCertsConfigTests(TestCaseInTempDir):
47
48 def test_default_raises_value_error(self):
49 stack = stack_from_string("")
50 self.overrideAttr(_urllib2_wrappers, "DEFAULT_CA_PATH",
51 "/i-do-not-exist")
52 self.assertRaises(ValueError, stack.get, 'ssl.ca_certs')
53
54 def test_default_exists(self):
55 self.build_tree(['cacerts.pem'])
56 stack = stack_from_string("")
57 path = os.path.join(self.test_dir, "cacerts.pem")
58 self.overrideAttr(_urllib2_wrappers, "DEFAULT_CA_PATH", path)
59 self.assertEquals(path, stack.get('ssl.ca_certs'))
60
61 def test_specified(self):
62 self.build_tree(['cacerts.pem'])
63 path = os.path.join(self.test_dir, "cacerts.pem")
64 stack = stack_from_string("ssl.ca_certs = %s\n" % path)
65 self.assertEquals(path, stack.get('ssl.ca_certs'))
66
67 def test_specified_doesnt_exist(self):
68 path = os.path.join(self.test_dir, "nonexisting.pem")
69 stack = stack_from_string("ssl.ca_certs = %s\n" % path)
70 self.warnings = []
71 def warning(*args):
72 self.warnings.append(args[0] % args[1:])
73 self.overrideAttr(trace, 'warning', warning)
74 self.assertEquals(_urllib2_wrappers.DEFAULT_CA_PATH, stack.get('ssl.ca_certs'))
75 self.assertLength(1, self.warnings)
76 self.assertContainsRe(self.warnings[0], "is not valid for \"ssl.ca_certs\"")
77
78
79class CertReqsConfigTests(TestCaseInTempDir):
80
81 def test_default(self):
82 stack = stack_from_string("")
83 self.assertEquals(ssl.CERT_REQUIRED, stack.get("ssl.cert_reqs"))
84
85 def test_from_string(self):
86 stack = stack_from_string("ssl.cert_reqs = none\n")
87 self.assertEquals(ssl.CERT_NONE, stack.get("ssl.cert_reqs"))
88 stack = stack_from_string("ssl.cert_reqs = optional\n")
89 self.assertEquals(ssl.CERT_OPTIONAL, stack.get("ssl.cert_reqs"))
90 stack = stack_from_string("ssl.cert_reqs = required\n")
91 self.assertEquals(ssl.CERT_REQUIRED, stack.get("ssl.cert_reqs"))
92 stack = stack_from_string("ssl.cert_reqs = invalid\n")
93 self.assertRaises(ConfigOptionValueError, stack.get, "ssl.cert_reqs")
94
95
96class MatchHostnameTests(TestCase):
97
98 def test_no_certificate(self):
99 self.assertRaises(ValueError, _urllib2_wrappers.match_hostname({}))
100
101 def test_no_valid_attributes(self):
102 self.assertRaises(CertificateError,
103 _urllib2_wrappers.match_hostname({"Problem": "Solved"}))
104
105 def test_common_name(self):
106 cert = {'subject': ((('commonName', 'example.com'),),)}
107 self.assertIs(None, _urllib2_wrappers.match_hostname(cert, "example.com"))
108 self.assertRaises(CertificateError, _urllib2_wrappers.match_hostname,
109 cert, "example.org")
0110
=== modified file 'bzrlib/transport/__init__.py'
--- bzrlib/transport/__init__.py 2011-12-24 10:10:59 +0000
+++ bzrlib/transport/__init__.py 2012-01-03 16:18:27 +0000
@@ -1782,15 +1782,15 @@
1782 help="Read-only access of branches exported on the web.")1782 help="Read-only access of branches exported on the web.")
1783register_transport_proto('https://',1783register_transport_proto('https://',
1784 help="Read-only access of branches exported on the web using SSL.")1784 help="Read-only access of branches exported on the web using SSL.")
1785# The default http implementation is urllib, but https is pycurl if available1785# The default http implementation is urllib
1786register_lazy_transport('http://', 'bzrlib.transport.http._pycurl',1786register_lazy_transport('http://', 'bzrlib.transport.http._pycurl',
1787 'PyCurlTransport')1787 'PyCurlTransport')
1788register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
1789 'PyCurlTransport')
1788register_lazy_transport('http://', 'bzrlib.transport.http._urllib',1790register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
1789 'HttpTransport_urllib')1791 'HttpTransport_urllib')
1790register_lazy_transport('https://', 'bzrlib.transport.http._urllib',1792register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
1791 'HttpTransport_urllib')1793 'HttpTransport_urllib')
1792register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
1793 'PyCurlTransport')
17941794
1795register_transport_proto('ftp://', help="Access using passive FTP.")1795register_transport_proto('ftp://', help="Access using passive FTP.")
1796register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')1796register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')
17971797
=== modified file 'bzrlib/transport/http/_urllib2_wrappers.py'
--- bzrlib/transport/http/_urllib2_wrappers.py 2011-12-19 13:23:58 +0000
+++ bzrlib/transport/http/_urllib2_wrappers.py 2012-01-03 16:18:27 +0000
@@ -14,7 +14,7 @@
14# along with this program; if not, write to the Free Software14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1616
17"""Implementaion of urllib2 tailored to bzr needs17"""Implementation of urllib2 tailored to bzr needs
1818
19This file complements the urllib2 class hierarchy with custom classes.19This file complements the urllib2 class hierarchy with custom classes.
2020
@@ -50,6 +50,7 @@
5050
51import errno51import errno
52import httplib52import httplib
53import os
53import socket54import socket
54import urllib55import urllib
55import urllib256import urllib2
@@ -63,12 +64,68 @@
63 config,64 config,
64 debug,65 debug,
65 errors,66 errors,
67 lazy_import,
66 osutils,68 osutils,
67 trace,69 trace,
68 transport,70 transport,
69 urlutils,71 urlutils,
70 )72 )
7173lazy_import.lazy_import(globals(), """
74import ssl
75""")
76
77DEFAULT_CA_PATH = u"/etc/ssl/certs/ca-certificates.crt"
78
79
80def default_ca_certs():
81 if not os.path.exists(DEFAULT_CA_PATH):
82 raise ValueError("default ca certs path %s does not exist" %
83 DEFAULT_CA_PATH)
84 return DEFAULT_CA_PATH
85
86
87def ca_certs_from_store(path):
88 if not os.path.exists(path):
89 raise ValueError("ca certs path %s does not exist" % path)
90 return path
91
92
93def default_cert_reqs():
94 return u"required"
95
96
97def cert_reqs_from_store(unicode_str):
98 import ssl
99 try:
100 return {
101 "required": ssl.CERT_REQUIRED,
102 "optional": ssl.CERT_OPTIONAL,
103 "none": ssl.CERT_NONE
104 }[unicode_str]
105 except KeyError:
106 raise ValueError("invalid value %s" % unicode_str)
107
108
109opt_ssl_ca_certs = config.Option('ssl.ca_certs',
110 from_unicode=ca_certs_from_store,
111 default=default_ca_certs,
112 invalid='warning',
113 help="""\
114Path to certification authority certificates to trust.
115""")
116
117opt_ssl_cert_reqs = config.Option('ssl.cert_reqs',
118 default=default_cert_reqs,
119 from_unicode=cert_reqs_from_store,
120 invalid='error',
121 help="""\
122Whether to require a certificate from the remote side. (default:required)
123
124Possible values:
125 * none: certificates ignored
126 * optional: Certificates not required, but validated if provided
127 * required: Certificates required, and validated
128""")
72129
73checked_kerberos = False130checked_kerberos = False
74kerberos = None131kerberos = None
@@ -312,17 +369,60 @@
312 self._wrap_socket_for_reporting(self.sock)369 self._wrap_socket_for_reporting(self.sock)
313370
314371
315# Build the appropriate socket wrapper for ssl372# These two methods were imported from Python 3.2's ssl module
316try:373
317 # python 2.6 introduced a better ssl package374def _dnsname_to_pat(dn):
318 import ssl375 pats = []
319 _ssl_wrap_socket = ssl.wrap_socket376 for frag in dn.split(r'.'):
320except ImportError:377 if frag == '*':
321 # python versions prior to 2.6 don't have ssl and ssl.wrap_socket instead378 # When '*' is a fragment by itself, it matches a non-empty dotless
322 # they use httplib.FakeSocket379 # fragment.
323 def _ssl_wrap_socket(sock, key_file, cert_file):380 pats.append('[^.]+')
324 ssl_sock = socket.ssl(sock, key_file, cert_file)381 else:
325 return httplib.FakeSocket(sock, ssl_sock)382 # Otherwise, '*' matches any dotless fragment.
383 frag = re.escape(frag)
384 pats.append(frag.replace(r'\*', '[^.]*'))
385 return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
386
387
388def match_hostname(cert, hostname):
389 """Verify that *cert* (in decoded format as returned by
390 SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
391 are mostly followed, but IP addresses are not accepted for *hostname*.
392
393 CertificateError is raised on failure. On success, the function
394 returns nothing.
395 """
396 if not cert:
397 raise ValueError("empty or no certificate")
398 dnsnames = []
399 san = cert.get('subjectAltName', ())
400 for key, value in san:
401 if key == 'DNS':
402 if _dnsname_to_pat(value).match(hostname):
403 return
404 dnsnames.append(value)
405 if not san:
406 # The subject is only checked when subjectAltName is empty
407 for sub in cert.get('subject', ()):
408 for key, value in sub:
409 # XXX according to RFC 2818, the most specific Common Name
410 # must be used.
411 if key == 'commonName':
412 if _dnsname_to_pat(value).match(hostname):
413 return
414 dnsnames.append(value)
415 if len(dnsnames) > 1:
416 raise errors.CertificateError("hostname %r "
417 "doesn't match either of %s"
418 % (hostname, ', '.join(map(repr, dnsnames))))
419 elif len(dnsnames) == 1:
420 raise errors.CertificateError("hostname %r "
421 "doesn't match %r"
422 % (hostname, dnsnames[0]))
423 else:
424 raise errors.CertificateError("no appropriate commonName or "
425 "subjectAltName fields were found")
326426
327427
328class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):428class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
@@ -345,7 +445,34 @@
345 self.connect_to_origin()445 self.connect_to_origin()
346446
347 def connect_to_origin(self):447 def connect_to_origin(self):
348 ssl_sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)448 # FIXME JRV 2011-12-18: Use location config here?
449 config_stack = config.GlobalStack()
450 ca_certs = config_stack.get('ssl.ca_certs')
451 cert_reqs = config_stack.get('ssl.cert_reqs')
452 if cert_reqs == ssl.CERT_NONE:
453 trace.warning("not checking SSL certificates for %s: %d",
454 self.host, self.port)
455 else:
456 trace.warning(
457 "no valid trusted SSL CA certificates file set. See "
458 "'bzr help ssl.ca_certs' for more information on setting "
459 "trusted CA's.")
460 try:
461 ssl_sock = ssl.wrap_socket(self.sock, self.key_file, self.cert_file,
462 cert_reqs=cert_reqs, ca_certs=ca_certs)
463 except ssl.SSLError, e:
464 if e.errno != ssl.SSL_ERROR_SSL:
465 raise
466 trace.note(
467 "To disable SSL certificate verification, use "
468 "-Ossl.cert_reqs=none. See ``bzr help ssl.ca_certs`` for "
469 "more information on specifying trusted CA certificates.")
470 raise
471 peer_cert = ssl_sock.getpeercert()
472 if (cert_reqs == ssl.CERT_REQUIRED or
473 (cert_reqs == ssl.CERT_OPTIONAL and peer_cert)):
474 match_hostname(peer_cert, self.host)
475
349 # Wrap the ssl socket before anybody use it476 # Wrap the ssl socket before anybody use it
350 self._wrap_socket_for_reporting(ssl_sock)477 self._wrap_socket_for_reporting(ssl_sock)
351478
352479
=== modified file 'doc/en/release-notes/bzr-2.5.txt'
--- doc/en/release-notes/bzr-2.5.txt 2012-01-03 13:21:09 +0000
+++ doc/en/release-notes/bzr-2.5.txt 2012-01-03 16:18:27 +0000
@@ -454,6 +454,12 @@
454 to 'line-based'. This replace the previous shelf UI only patch using454 to 'line-based'. This replace the previous shelf UI only patch using
455 ``INSIDE_EMACS``. (Benoît Pierre)455 ``INSIDE_EMACS``. (Benoît Pierre)
456456
457* urllib-based HTTPS client connections now verify the server certificate
458 as well as the hostname. (Jelmer Vernooij)
459
460* urllib-based HTTPS connections are now the default.
461 (Jelmer Vernooij, #125055, #651161)
462
457Bug Fixes463Bug Fixes
458*********464*********
459465
460466
=== modified file 'doc/en/whats-new/whats-new-in-2.5.txt'
--- doc/en/whats-new/whats-new-in-2.5.txt 2011-12-14 15:54:07 +0000
+++ doc/en/whats-new/whats-new-in-2.5.txt 2012-01-03 16:18:27 +0000
@@ -36,6 +36,23 @@
36encoding for interacting with the filesystem. This makes creating a working36encoding for interacting with the filesystem. This makes creating a working
37tree and commiting to it possible for such branches in most environments.37tree and commiting to it possible for such branches in most environments.
3838
39SSL Certificate Verification Support in urllib HTTPS backend
40************************************************************
41
42In previous versions of Bazaar, only one of the two supported HTTPS
43backends, pycurl, supported verification of SSL certificates. This version
44also introduces this support for the urllib backend.
45
46Along with this support two new options have been introduced to control
47which CA's are trusted and to what degree server certificates should be
48verified. See ``bzr help ssl.ca_certs`` and ``bzr help ssl.cert_reqs``
49for more information
50
51Users who have previously used the urllib HTTPS backend with servers
52with invalid or untrusted certificates can continue to do so by
53adding the required certificates to the trusted CA certificate file
54(recommended) or by setting the ``ssl.cert_reqs`` option to ``none``.
55
39Further information56Further information
40*******************57*******************
4158