Skip to content

No SSL match_hostname() in ftp, imap, nntp, pop, smtp modules #63708

Closed

Description

@tiran
BPO 19509
Nosy @gvanrossum, @birkenfeld, @pitrou, @larryhastings, @giampaolo, @tiran, @dstufft, @vajrasky
Dependencies
  • bpo-19781: No SSL match_hostname() in ftplib
  • bpo-19782: No SSL match_hostname() in imaplib
  • bpo-19783: No SSL match_hostname() in nntplib
  • bpo-19784: No SSL match_hostname() in poplib
  • bpo-19785: No SSL match_hostname() in smtplib
  • Files
  • sslctx_check_hostname.patch
  • check_hostname_adjustments.patch
  • asyncio_ssl_verify.patch
  • asyncio_ssl_verify2.patch
  • asyncio_ssl_verify3.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2013-12-06.13:16:29.681>
    created_at = <Date 2013-11-05.22:57:12.554>
    labels = ['type-security']
    title = 'No SSL match_hostname() in ftp, imap, nntp, pop, smtp modules'
    updated_at = <Date 2013-12-06.13:16:29.680>
    user = 'https://github.com/tiran'

    bugs.python.org fields:

    activity = <Date 2013-12-06.13:16:29.680>
    actor = 'christian.heimes'
    assignee = 'none'
    closed = True
    closed_date = <Date 2013-12-06.13:16:29.681>
    closer = 'christian.heimes'
    components = []
    creation = <Date 2013-11-05.22:57:12.554>
    creator = 'christian.heimes'
    dependencies = ['19781', '19782', '19783', '19784', '19785']
    files = ['32881', '32940', '32967', '32991', '32996']
    hgrepos = []
    issue_num = 19509
    keywords = ['patch']
    message_count = 30.0
    messages = ['202246', '204318', '204518', '204519', '204520', '204521', '204522', '204523', '204524', '204526', '204532', '204672', '204673', '204681', '204683', '204989', '205052', '205064', '205198', '205199', '205203', '205231', '205241', '205279', '205309', '205319', '205336', '205338', '205351', '205368']
    nosy_count = 11.0
    nosy_names = ['gvanrossum', 'georg.brandl', 'janssen', 'pitrou', 'larry', 'giampaolo.rodola', 'christian.heimes', 'Arfrever', 'python-dev', 'dstufft', 'vajrasky']
    pr_nums = []
    priority = 'high'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'security'
    url = 'https://bugs.python.org/issue19509'
    versions = ['Python 3.4']

    Activity

    1. tiran commented on Nov 5, 2013

      @tiran
      MemberAuthor

      None of the TLS/SSL classes for ftp, imap, nntp, pop and smtp have support for match_hostname() in order to verify that the SSL cert matches the host name. I'm not sure how we can handle the problem without creating backwards incompatibilities.

    2. tiran commented on Nov 25, 2013

      @tiran
      MemberAuthor

      The patch implements check_hostname in order to match SSL certs with the peer's hostname in ftp, imap, nntp, pop and smtp library. So far the patch needs more tests and doc updates.

      I consider the new feature a security fix. Right now everybody with any valid TLS/SSL certificate can claim that its certificate is valid for 'smtp.google.com'.

    3. larryhastings commented on Nov 26, 2013

      @larryhastings
      Contributor

      I don't know enough about SSL to make a "feature vs bug/security fix" ruling on this. Can a second person who does--maybe Bill Janssen?--chime in?

    4. dstufft commented on Nov 26, 2013

      @dstufft
      Member

      I agree with Christian, mail.stufft.io should not be able to masquerade as smtp.google.com and being able to do so is a pretty big security hole.

    5. pitrou commented on Nov 26, 2013

      @pitrou
      Member

      I think adding an API in bugfix releases must be an exceptional decision and I'm honestly not convinced this issue justifies it.
      It'd be more convincing if there was actually demand for this feature (e.g. from higher-level library authors).

    6. dstufft commented on Nov 26, 2013

      @dstufft
      Member

      Probably the higher level libraries don't even realize it's not happening, it's similar to the issue of SSL validation for HTTPS connections where a vast swathe of people didn't even realize that their certificates weren't being validated.

    7. tiran commented on Nov 26, 2013

      @tiran
      MemberAuthor

      I agree with Antoine. The five attached bug reports only refer to Python 3.4.

    8. pitrou commented on Nov 26, 2013

      @pitrou
      Member

      Probably the higher level libraries don't even realize it's not
      happening, it's similar to the issue of SSL validation for HTTPS
      connections where a vast swathe of people didn't even realize that
      their certificates weren't being validated.

      Exactly. And we didn't add certificate checking in bugfix releases in
      the name of security.

      Now I appreciate that it would be a bit of a pity to wait for 3.5 to add
      this, so perhaps Larry wants to make an exception to the 3.4 feature
      freeze so that this feature can come in (assuming Christian's patch is
      ready).

    9. birkenfeld commented on Nov 26, 2013

      @birkenfeld
      Member

      For a true security fix, the default for check_hostname would have to be True. However, that will create a lot of backward compatibility problems for questionable gain.

      I think Larry should make an exception for 3.4 and allow this new feature.

    10. larryhastings commented on Nov 26, 2013

      @larryhastings
      Contributor

      Okay, you have my permission to add match_hostname() and fix the security hole you cite. Can you have it done soon, so it can bake for a while in trunk before we cut beta 2?

    11. dstufft commented on Nov 26, 2013

      @dstufft
      Member

      I assumed we were talking about 3.4 and didn't even notice that the issues had 3.3 and 3.2 attached to it.

    12. tiran commented on Nov 28, 2013

      @tiran
      MemberAuthor

      My patch could be much simpler and easier if we could just drop support for ancient versions of OpenSSL. My idea requires at least OpenSSL 0.9.8f (release 2007) with SNI support. Six years are a lot for crypto software. All relevant platforms with vendor support have a more recent version of OpenSSL, too.

      >> context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
      >> context.verify_mode = ssl.CERT_REQUIRED
      >> context.check_hostname = True
      >> context.wrap_socket(sock, server_hostname="www.example.org")

      server_hostname is used to for server name indicator (SNI) as well as the hostname for match_hostname(). It would remove lots and lots of code duplication, too.

      The check_hostname takes care about invalid combinations, too:

      >>> context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
      >>> context.verify_mode == ssl.CERT_NONE
      True
      >>> context.check_hostname = True
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: check_hostname needs a SSL context with either CERT_OPTIONAL or CERT_REQUIRED
    13. pitrou commented on Nov 28, 2013

      @pitrou
      Member

      My patch could be much simpler and easier if we could just drop
      support for ancient versions of OpenSSL. My idea requires at least
      OpenSSL 0.9.8f (release 2007) with SNI support.

      What are you talking about? Your patch doesn't use HAS_SNI.

    14. tiran commented on Nov 28, 2013

      @tiran
      MemberAuthor

      The patches in the dependency tickets are using SNI. The problem is, a non-None server_hostname argument raises an error when OpenSSL doesn't support the feature.

      Here is a demo patch for my idea. It makes it very easy to add hostname matching to existing code. All it takes is the "server_hostname" argument to wrap_socket() and a new property "check_hostname" for the SSLContext object. The rest is done in do_handshake().

    15. 3 remaining items

    16. gvanrossum commented on Dec 2, 2013

      @gvanrossum
      Member

      The asyncio patch looks fine, but I'd like to see a unittest that actually fails (or mocks failing) the hostname check where it is now performed (in wrap_socket() IIUC?), to make sure that the exception is propagated out properly.

    17. tiran commented on Dec 4, 2013

      @tiran
      MemberAuthor

      I'd rather have integration test with a real SSL connection in order to check the entire stack. asyncio doesn't have a test for CERT_REQUIRED yet. The attached patch tests three common cases: missing CA, missing server_hostname and a successful connection with full verification.

    18. tiran commented on Dec 4, 2013

      @tiran
      MemberAuthor

      PS: The test uses keycert3.pem and pycacert.pem from the 3.4 test directory. keycert3.pem contains privat + public key and is signed by the CA in pycacert.pem.

    19. vajrasky commented on Dec 4, 2013

      vajraskymannequin
      Mannequin

      I left my comment on the review. I forgot to mail the review.

    20. gvanrossum commented on Dec 4, 2013

      @gvanrossum
      Member

      See my messages on the review. Is it absolutely necessary to close the socket when the hostname verification fails?

    21. python-dev commented on Dec 4, 2013

      python-devmannequin
      Mannequin

      New changeset b6fce698e467 by Christian Heimes in branch 'default':
      Issue bpo-19509: Don't close the socket in do_handshake() when hostname verification fails.
      http://hg.python.org/cpython/rev/b6fce698e467

    22. python-dev commented on Dec 5, 2013

      python-devmannequin
      Mannequin

      New changeset 1a945fb875bf by Christian Heimes in branch 'default':
      bpo-19509: Don't call match_hostname() twice in http.client.
      http://hg.python.org/cpython/rev/1a945fb875bf

    23. tiran commented on Dec 5, 2013

      @tiran
      MemberAuthor

      The new patch splits the test into three separate test cases.

      Guido, Python 3.3 doesn't have the necessary CA and server cert files. The files were added to 3.4 for some other tests. The patch tries to load the files from 3.4's test directory and falls back to local copies. You have to copy/delete these files to test the patch:

      $ hg rm Lib/test/test_asyncio/sample.*
      $ hg cp Lib/test/ssl_key.pem Lib/test/ssl_cert.pem Lib/test/pycacert.pem Lib/test/keycert3.pem  Lib/test/test_asyncio/
    24. gvanrossum commented on Dec 5, 2013

      @gvanrossum
      Member

      Thanks Christian, this is almost right. I'm uploading a patch that covers all bases:

      • Works on Python 3.3 (TEST_HOME_DIR isn't defined there)
      • Works on older Python 3.4 versions (check_hostname may not exist)
      • Works on latest Python 3.4 version
    25. python-dev commented on Dec 5, 2013

      python-devmannequin
      Mannequin

      New changeset 1605eda93392 by Christian Heimes in branch 'default':
      Issue bpo-19509: Finish implementation of check_hostname
      http://hg.python.org/cpython/rev/1605eda93392

    26. gvanrossum commented on Dec 6, 2013

      @gvanrossum
      Member

      With the latest (revision 1605eda93392) I get four failures on OS X. Three are like this (in all three selector types -- kqueue, select, poll):

      ======================================================================
      ERROR: test_create_ssl_connection (test_events.SelectEventLoopTests)
      ----------------------------------------------------------------------

      Traceback (most recent call last):
        File "tests/test_events.py", line 532, in test_create_ssl_connection
          tr, pr = self.loop.run_until_complete(f)
        File "/Users/guido/tulip/asyncio/base_events.py", line 177, in run_until_complete
          return future.result()
        File "/Users/guido/tulip/asyncio/futures.py", line 221, in result
          raise self._exception
        File "/Users/guido/tulip/asyncio/tasks.py", line 276, in _step
          result = coro.throw(exc)
        File "/Users/guido/tulip/asyncio/base_events.py", line 388, in create_connection
          yield from waiter
        File "/Users/guido/tulip/asyncio/futures.py", line 320, in __iter__
          yield self  # This tells Task to wait for completion.
        File "/Users/guido/tulip/asyncio/tasks.py", line 329, in _wakeup
          value = future.result()
        File "/Users/guido/tulip/asyncio/futures.py", line 221, in result
          raise self._exception
        File "/Users/guido/tulip/asyncio/selector_events.py", line 618, in _on_handshake
          self._sock.do_handshake()
        File "/usr/local/lib/python3.4/ssl.py", line 748, in do_handshake
          self._sslobj.do_handshake()
      ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:599)

      The last is similar in test_streams.py:

      ======================================================================
      ERROR: test_open_connection_no_loop_ssl (test_streams.StreamReaderTests)
      ----------------------------------------------------------------------

      Traceback (most recent call last):
        File "tests/test_streams.py", line 58, in test_open_connection_no_loop_ssl
          reader, writer = self.loop.run_until_complete(f)
        File "/Users/guido/tulip/asyncio/base_events.py", line 177, in run_until_complete
          return future.result()
        File "/Users/guido/tulip/asyncio/futures.py", line 221, in result
          raise self._exception
        File "/Users/guido/tulip/asyncio/tasks.py", line 276, in _step
          result = coro.throw(exc)
        File "/Users/guido/tulip/asyncio/streams.py", line 43, in open_connection
          lambda: protocol, host, port, **kwds)
        File "/Users/guido/tulip/asyncio/base_events.py", line 388, in create_connection
          yield from waiter
        File "/Users/guido/tulip/asyncio/futures.py", line 320, in __iter__
          yield self  # This tells Task to wait for completion.
        File "/Users/guido/tulip/asyncio/tasks.py", line 329, in _wakeup
          value = future.result()
        File "/Users/guido/tulip/asyncio/futures.py", line 221, in result
          raise self._exception
        File "/Users/guido/tulip/asyncio/selector_events.py", line 618, in _on_handshake
          self._sock.do_handshake()
        File "/usr/local/lib/python3.4/ssl.py", line 748, in do_handshake
          self._sslobj.do_handshake()
      ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:599)

      I get the same failures when I copy the changes to the Tulip repo.

    27. gvanrossum commented on Dec 6, 2013

      @gvanrossum
      Member

      That's fixed by revision ec1e7fc9b5a4.

      Christian, can this bug be closed now, or is there more?

    28. tiran commented on Dec 6, 2013

      @tiran
      MemberAuthor

      I'm closing this ticket. All features have been implemented and tests are passing, too.

      I have created bpo-19909 as a reminder for doc improvements.

    29. transferred this issue fromon Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions