GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

test_onion_security_expectations.py 6.11 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
from marionette_driver import By, Wait
from marionette_driver.errors import MarionetteException, NoSuchElementException, TimeoutException
from marionette_driver.legacy_actions import Actions
from marionette_harness import MarionetteTestCase, WindowManagerMixin

import testsuite

from stem.control import Controller

from urlparse import urlparse

import time

class OnionFixturesMixin(object):
    def setUp(self):
        super(OnionFixturesMixin, self).setUp()
        tor_control_port = testsuite.TestSuite(
        ).t['options']['tor-control-port']
        with Controller.from_port(port=int(tor_control_port)) as controller:
            controller.authenticate()
            port = urlparse(self.marionette.absolute_url('')).port
            response = controller.create_ephemeral_hidden_service(
                {80: port},
                key_content='ED25519-V3',
                await_publication=True,
                detached=True,
            )
            self.service_id = response.service_id

    def tearDown(self):
        tor_control_port = testsuite.TestSuite(
        ).t['options']['tor-control-port']
        with Controller.from_port(port=int(tor_control_port)) as controller:
            controller.authenticate()
            controller.remove_ephemeral_hidden_service(self.service_id)
        super(OnionFixturesMixin, self).tearDown()


class Test(OnionFixturesMixin, WindowManagerMixin, MarionetteTestCase):
    def get_identity_class(self):
        with self.marionette.using_context('chrome'):
            return self.marionette.execute_script('''
                return document.getElementById("identity-box").className;
            ''')

    def get_identity_icon(self):
        with self.marionette.using_context('chrome'):
            return self.marionette.execute_script('''
                const el = document.getElementById("identity-icon");
                return document.defaultView.getComputedStyle(el)["list-style-image"];
            ''')

    def get_connection_type(self):
        m = self.marionette
        with self.marionette.using_context('chrome'):
            self.execute_chrome(
                'document.getElementById("identity-popup-more-info").click()')
            m.switch_to_window(m.chrome_window_handles[1])
            Wait(m, timeout=m.timeout.page_load).until(
                lambda _: m.find_element('id', 'security-technical-shortform').text != '')
            text = m.find_element('id', 'security-technical-shortform').text
            m.close_chrome_window()
            m.switch_to_window(self.start_window)
            return text

    def execute_chrome(self, script):
        with self.marionette.using_context('chrome'):
            return self.marionette.execute_script(script)

    def test_onion_security_expectations(self):
        m = self.marionette
        m.timeout.implicit = 10

        # Wait some more time to make sure the onion service is set up
        time.sleep(10)

        # Regular onion
        with m.using_context('content'):
            m.navigate('http://' + self.service_id + '.onion')
            self.assertTrue(self.execute_chrome(
                "return !!gIdentityHandler._isSecureConnection;"))
            self.assertEqual(self.get_identity_class(), 'onionUnknownIdentity')
            self.assertEqual(self.get_connection_type(),
                             'Connection Encrypted (Onion Service)')
            self.assertEqual(self.get_identity_icon(),
                             'url("chrome://browser/skin/onion.svg")')

        # Onion with mixed display content
        with m.using_context('content'):
            m.navigate('http://' + self.service_id + '.onion/mixed.html')
            self.assertFalse(self.execute_chrome(
                "return !!gIdentityHandler._isSecureConnection;"))
            self.assertEqual(self.get_identity_class(),
                             'onionUnknownIdentity onionMixedDisplayContent')
            self.assertEqual(self.get_connection_type(),
                             'Connection Partially Encrypted')
            self.assertEqual(self.get_identity_icon(),
                             'url("chrome://browser/skin/onion-warning.svg")')

        # Onion with mixed active content
        with m.using_context('content'):
            m.navigate('http://' + self.service_id +
                       '.onion/mixed_active.html')
            self.assertTrue(self.execute_chrome(
                "return !!gIdentityHandler._isSecureConnection;"))
            self.assertEqual(self.get_identity_class(), 'onionUnknownIdentity')
            self.assertEqual(self.get_connection_type(),
                             'Connection Encrypted (Onion Service)')
            self.assertEqual(self.get_identity_icon(),
                             'url("chrome://browser/skin/onion.svg")')
            # Reload with mixed content protection disabled
            self.execute_chrome(
                'gIdentityHandler.disableMixedContentProtection();')
            Wait(m, timeout=m.timeout.page_load).until(
                lambda _: self.get_identity_class() != 'onionUnknownIdentity')
            self.assertFalse(self.execute_chrome(
                "return !!gIdentityHandler._isSecureConnection;"))
            self.assertEqual(self.get_identity_class(),
                             'onionUnknownIdentity onionMixedActiveContent')
            self.assertEqual(self.get_connection_type(),
                             'Connection Partially Encrypted')
            self.assertEqual(self.get_identity_icon(),
                             'url("chrome://browser/skin/onion-slash.svg")')

        # Onion with valid TLS certificate
        with m.using_context('content'):
            m.navigate('https://3g2upl4pq6kufc4m.onion/')
            self.assertTrue(self.execute_chrome(
                "return !!gIdentityHandler._isSecureConnection;"))
            self.assertEqual(self.get_identity_class(), 'onionVerifiedDomain')
            self.assertEqual(self.get_connection_type(
            ), 'Connection Encrypted (Onion Service, TLS_AES_256_GCM_SHA384, 256 bit keys, TLS 1.3)')
            self.assertEqual(self.get_identity_icon(),
                             'url("chrome://browser/skin/onion.svg")')