Exemplo n.º 1
0
class TestKeyList(unittest.TestCase):
    def setUp(self):
        self.unittest_dir = cd_unittest()
        self.master_key = MasterKey()
        self.settings = Settings()
        self.file_name = f'{DIR_USER_DATA}{self.settings.software_operation}_keys'
        self.keylist = KeyList(self.master_key, self.settings)
        self.full_contact_list = ['Alice', 'Bob', 'Charlie', LOCAL_ID]
        self.keylist.keysets = [
            create_keyset(n, store_f=self.keylist.store_keys)
            for n in self.full_contact_list
        ]

    def tearDown(self):
        cleanup(self.unittest_dir)

    def test_storing_and_loading_of_keysets(self):
        # Test store
        self.keylist.store_keys()
        self.assertEqual(
            os.path.getsize(self.file_name), XCHACHA20_NONCE_LENGTH +
            (self.settings.max_number_of_contacts + 1) * KEYSET_LENGTH +
            POLY1305_TAG_LENGTH)

        # Test load
        key_list2 = KeyList(MasterKey(), Settings())
        self.assertEqual(len(key_list2.keysets), len(self.full_contact_list))

    def test_load_of_modified_database_raises_critical_error(self):
        self.keylist.store_keys()

        # Test reading works normally
        self.assertIsInstance(KeyList(self.master_key, self.settings), KeyList)

        # Test loading of the tampered database raises CriticalError
        tamper_file(self.file_name, tamper_size=1)
        with self.assertRaises(SystemExit):
            KeyList(self.master_key, self.settings)

    def test_invalid_content_raises_critical_error(self):
        # Setup
        invalid_data = b'a'
        pt_bytes = b''.join([
            k.serialize_k()
            for k in self.keylist.keysets + self.keylist._dummy_keysets()
        ])
        ct_bytes = encrypt_and_sign(pt_bytes + invalid_data,
                                    self.master_key.master_key)

        ensure_dir(DIR_USER_DATA)
        with open(self.file_name, 'wb+') as f:
            f.write(ct_bytes)

        # Test
        with self.assertRaises(SystemExit):
            KeyList(self.master_key, self.settings)

    def test_generate_dummy_keyset(self):
        dummy_keyset = self.keylist.generate_dummy_keyset()
        self.assertEqual(len(dummy_keyset.serialize_k()), KEYSET_LENGTH)
        self.assertIsInstance(dummy_keyset, KeySet)

    def test_dummy_keysets(self):
        dummies = self.keylist._dummy_keysets()
        self.assertEqual(len(dummies),
                         (self.settings.max_number_of_contacts + 1) -
                         len(self.full_contact_list))
        for c in dummies:
            self.assertIsInstance(c, KeySet)

    def test_add_keyset(self):
        new_key = bytes(SYMMETRIC_KEY_LENGTH)
        self.keylist.keysets = [create_keyset(LOCAL_ID)]

        # Check that KeySet exists and that its keys are different
        self.assertNotEqual(self.keylist.keysets[0].rx_hk, new_key)

        # Replace existing KeySet
        self.assertIsNone(
            self.keylist.add_keyset(LOCAL_PUBKEY, new_key, new_key, new_key,
                                    new_key))

        # Check that new KeySet replaced the old one
        self.assertEqual(self.keylist.keysets[0].onion_pub_key, LOCAL_PUBKEY)
        self.assertEqual(self.keylist.keysets[0].rx_hk, new_key)

    def test_remove_keyset(self):
        # Test KeySet for Bob exists
        self.assertTrue(self.keylist.has_keyset(nick_to_pub_key('Bob')))

        # Remove KeySet for Bob
        self.assertIsNone(self.keylist.remove_keyset(nick_to_pub_key('Bob')))

        # Test KeySet was removed
        self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('Bob')))

    def test_change_master_key(self):
        key = SYMMETRIC_KEY_LENGTH * b'\x01'
        master_key2 = MasterKey(master_key=key)

        # Test that new key is different from existing one
        self.assertNotEqual(key, self.master_key.master_key)

        # Change master key
        self.assertIsNone(self.keylist.change_master_key(master_key2))

        # Test that master key has changed
        self.assertEqual(self.keylist.master_key.master_key, key)

        # Test that loading of the database with new key succeeds
        self.assertIsInstance(KeyList(master_key2, self.settings), KeyList)

    def test_update_database(self):
        self.assertEqual(os.path.getsize(self.file_name), 9016)
        self.assertIsNone(
            self.keylist.manage(KDB_UPDATE_SIZE_HEADER,
                                Settings(max_number_of_contacts=100)))
        self.assertEqual(os.path.getsize(self.file_name), 17816)
        self.assertEqual(self.keylist.settings.max_number_of_contacts, 100)

    def test_get_keyset(self):
        keyset = self.keylist.get_keyset(nick_to_pub_key('Alice'))
        self.assertIsInstance(keyset, KeySet)

    def test_get_list_of_pub_keys(self):
        self.assertEqual(self.keylist.get_list_of_pub_keys(), [
            nick_to_pub_key("Alice"),
            nick_to_pub_key("Bob"),
            nick_to_pub_key("Charlie")
        ])

    def test_has_keyset(self):
        self.keylist.keysets = []
        self.assertFalse(self.keylist.has_keyset(nick_to_pub_key("Alice")))

        self.keylist.keysets = [create_keyset('Alice')]
        self.assertTrue(self.keylist.has_keyset(nick_to_pub_key("Alice")))

    def test_has_rx_mk(self):
        self.assertTrue(self.keylist.has_rx_mk(nick_to_pub_key('Bob')))
        self.keylist.get_keyset(
            nick_to_pub_key('Bob')).rx_mk = bytes(SYMMETRIC_KEY_LENGTH)
        self.keylist.get_keyset(
            nick_to_pub_key('Bob')).rx_hk = bytes(SYMMETRIC_KEY_LENGTH)
        self.assertFalse(self.keylist.has_rx_mk(nick_to_pub_key('Bob')))

    def test_has_local_keyset(self):
        self.keylist.keysets = []
        self.assertFalse(self.keylist.has_local_keyset())

        self.assertIsNone(
            self.keylist.add_keyset(LOCAL_PUBKEY, bytes(SYMMETRIC_KEY_LENGTH),
                                    bytes(SYMMETRIC_KEY_LENGTH),
                                    bytes(SYMMETRIC_KEY_LENGTH),
                                    bytes(SYMMETRIC_KEY_LENGTH)))
        self.assertTrue(self.keylist.has_local_keyset())

    def test_manage(self):
        # Test that KeySet for David does not exist
        self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('David')))

        # Test adding KeySet
        self.assertIsNone(
            self.keylist.manage(KDB_ADD_ENTRY_HEADER, nick_to_pub_key('David'),
                                bytes(SYMMETRIC_KEY_LENGTH),
                                bytes(SYMMETRIC_KEY_LENGTH),
                                bytes(SYMMETRIC_KEY_LENGTH),
                                bytes(SYMMETRIC_KEY_LENGTH)))
        self.assertTrue(self.keylist.has_keyset(nick_to_pub_key('David')))

        # Test removing KeySet
        self.assertIsNone(
            self.keylist.manage(KDB_REMOVE_ENTRY_HEADER,
                                nick_to_pub_key('David')))
        self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('David')))

        # Test changing master key
        new_key = SYMMETRIC_KEY_LENGTH * b'\x01'

        self.assertNotEqual(self.master_key.master_key, new_key)
        self.assertIsNone(
            self.keylist.manage(KDB_CHANGE_MASTER_KEY_HEADER,
                                MasterKey(master_key=new_key)))
        self.assertEqual(self.keylist.master_key.master_key, new_key)

        # Test updating key_database with new settings changes database size.
        self.assertEqual(os.path.getsize(self.file_name), 9016)
        self.assertIsNone(
            self.keylist.manage(KDB_UPDATE_SIZE_HEADER,
                                Settings(max_number_of_contacts=100)))
        self.assertEqual(os.path.getsize(self.file_name), 17816)

        # Test invalid KeyList management command raises Critical Error
        with self.assertRaises(SystemExit):
            self.keylist.manage('invalid_key', None)
Exemplo n.º 2
0
class TestKeyList(unittest.TestCase):
    def setUp(self):
        self.master_key = MasterKey()
        self.settings = Settings()
        self.keylist = KeyList(MasterKey(), Settings())
        self.keylist.keysets = [
            create_keyset(n, store_f=self.keylist.store_keys)
            for n in ['Alice', 'Bob', 'Charlie']
        ]
        self.keylist.store_keys()

    def tearDown(self):
        cleanup()

    def test_storing_and_loading_of_keysets(self):
        # Test Store
        self.assertTrue(os.path.isfile(f'{DIR_USER_DATA}ut_keys'))
        self.assertEqual(
            os.path.getsize(f'{DIR_USER_DATA}ut_keys'), XSALSA20_NONCE_LEN +
            self.settings.max_number_of_contacts * KEYSET_LENGTH +
            POLY1305_TAG_LEN)

        # Test load
        keylist2 = KeyList(MasterKey(), Settings())
        self.assertEqual(len(keylist2.keysets), 3)

    def test_change_master_key(self):
        key = KEY_LENGTH * b'\x01'
        masterkey2 = MasterKey(master_key=key)
        self.keylist.change_master_key(masterkey2)
        self.assertEqual(self.keylist.master_key.master_key, key)

    def test_generate_dummy_keyset(self):
        dummy_keyset = self.keylist.generate_dummy_keyset()
        self.assertEqual(len(dummy_keyset.serialize_k()), KEYSET_LENGTH)
        self.assertIsInstance(dummy_keyset, KeySet)

    def test_get_keyset(self):
        keyset = self.keylist.get_keyset('*****@*****.**')
        self.assertIsInstance(keyset, KeySet)

    def test_has_local_key_and_add_keyset(self):
        self.assertFalse(self.keylist.has_local_key())
        self.assertIsNone(
            self.keylist.add_keyset(LOCAL_ID, bytes(KEY_LENGTH),
                                    bytes(KEY_LENGTH), bytes(KEY_LENGTH),
                                    bytes(KEY_LENGTH)))
        self.assertIsNone(
            self.keylist.add_keyset(LOCAL_ID, bytes(KEY_LENGTH),
                                    bytes(KEY_LENGTH), bytes(KEY_LENGTH),
                                    bytes(KEY_LENGTH)))
        self.assertTrue(self.keylist.has_local_key())

    def test_has_keyset_and_remove_keyset(self):
        self.assertTrue(self.keylist.has_keyset('*****@*****.**'))
        self.assertIsNone(self.keylist.remove_keyset('*****@*****.**'))
        self.assertFalse(self.keylist.has_keyset('*****@*****.**'))

    def test_has_rx_key(self):
        self.assertTrue(self.keylist.has_rx_key('*****@*****.**'))
        self.keylist.get_keyset('*****@*****.**').rx_key = bytes(KEY_LENGTH)
        self.keylist.get_keyset('*****@*****.**').rx_hek = bytes(KEY_LENGTH)
        self.assertFalse(self.keylist.has_rx_key('*****@*****.**'))

    def test_manage_keylist(self):
        self.assertFalse(self.keylist.has_keyset('*****@*****.**'))
        self.assertIsNone(
            self.keylist.manage(KDB_ADD_ENTRY_HEADER, '*****@*****.**',
                                bytes(KEY_LENGTH), bytes(KEY_LENGTH),
                                bytes(KEY_LENGTH), bytes(KEY_LENGTH)))
        self.assertTrue(self.keylist.has_keyset('*****@*****.**'))

        self.assertIsNone(
            self.keylist.manage(KDB_REMOVE_ENTRY_HEADER, '*****@*****.**'))
        self.assertFalse(self.keylist.has_keyset('*****@*****.**'))

        self.assertIsNone(
            self.keylist.manage(KDB_CHANGE_MASTER_KEY_HEADER,
                                MasterKey(master_key=KEY_LENGTH * b'\x01')))
        self.assertEqual(self.keylist.master_key.master_key,
                         KEY_LENGTH * b'\x01')

        with self.assertRaises(SystemExit):
            self.keylist.manage('invalid_key', None)
Exemplo n.º 3
0
def change_master_key(user_input: 'UserInput', contact_list: 'ContactList',
                      group_list: 'GroupList', settings: 'Settings',
                      queues: 'QueueDict', master_key: 'MasterKey',
                      onion_service: 'OnionService') -> None:
    """Change the master key on Transmitter/Receiver Program."""
    if settings.traffic_masking:
        raise SoftError("Error: Command is disabled during traffic masking.",
                        head_clear=True)

    try:
        device = user_input.plaintext.split()[1].lower()
    except IndexError:
        raise SoftError(
            f"Error: No target-system ('{TX}' or '{RX}') specified.",
            head_clear=True)

    if device not in [TX, RX]:
        raise SoftError(f"Error: Invalid target system '{device}'.",
                        head_clear=True)

    if device == RX:
        queue_command(CH_MASTER_KEY, settings, queues)
        return None

    authenticated = master_key.authenticate_action()

    if authenticated:
        # Cache old master key to allow log file re-encryption.
        old_master_key = master_key.master_key[:]

        # Create new master key but do not store new master key data into any database.
        new_master_key = master_key.master_key = master_key.new_master_key(
            replace=False)
        phase("Re-encrypting databases")

        # Halt `sender_loop` for the duration of database re-encryption.
        queues[KEY_MANAGEMENT_QUEUE].put((KDB_M_KEY_CHANGE_HALT_HEADER, ))
        wait_for_key_db_halt(queues)

        # Load old key_list from database file as it's not used on input_loop side.
        key_list = KeyList(master_key, settings)

        # Update encryption keys for databases
        contact_list.database.database_key = new_master_key
        key_list.database.database_key = new_master_key
        group_list.database.database_key = new_master_key
        settings.database.database_key = new_master_key
        onion_service.database.database_key = new_master_key

        # Create temp databases for each database, do not replace original.
        with ignored(SoftError):
            change_log_db_key(old_master_key, new_master_key, settings)
        contact_list.store_contacts(replace=False)
        key_list.store_keys(replace=False)
        group_list.store_groups(replace=False)
        settings.store_settings(replace=False)
        onion_service.store_onion_service_private_key(replace=False)

        # At this point all temp files exist and they have been checked to be valid by the respective
        # temp file writing function. It's now time to create a temp file for the new master key
        # database. Once the temp master key database is created, the `replace_database_data()` method
        # will also run the atomic `os.replace()` command for the master key database.
        master_key.replace_database_data()

        # Next we do the atomic `os.replace()` for all other files too.
        replace_log_db(settings)
        contact_list.database.replace_database()
        key_list.database.replace_database()
        group_list.database.replace_database()
        settings.database.replace_database()
        onion_service.database.replace_database()

        # Now all databases have been updated. It's time to let
        # the key database know what the new master key is.
        queues[KEY_MANAGEMENT_QUEUE].put(new_master_key)

        wait_for_key_db_ack(new_master_key, queues)

        phase(DONE)
        m_print("Master key successfully changed.",
                bold=True,
                tail_clear=True,
                delay=1,
                head=1)
Exemplo n.º 4
0
class TestKeyList(unittest.TestCase):
    def setUp(self) -> None:
        """Pre-test actions."""
        self.unit_test_dir = cd_unit_test()
        self.master_key = MasterKey()
        self.settings = Settings()
        self.file_name = f'{DIR_USER_DATA}{self.settings.software_operation}_keys'
        self.keylist = KeyList(self.master_key, self.settings)
        self.full_contact_list = ['Alice', 'Bob', 'Charlie', LOCAL_ID]
        self.keylist.keysets = [
            create_keyset(n, store_f=self.keylist.store_keys)
            for n in self.full_contact_list
        ]

    def tearDown(self) -> None:
        """Post-test actions."""
        cleanup(self.unit_test_dir)

    def test_storing_and_loading_of_keysets(self) -> None:
        # Test store
        self.keylist.store_keys()
        self.assertEqual(
            os.path.getsize(self.file_name), XCHACHA20_NONCE_LENGTH +
            (self.settings.max_number_of_contacts + 1) * KEYSET_LENGTH +
            POLY1305_TAG_LENGTH)

        # Test load
        key_list2 = KeyList(MasterKey(), Settings())
        self.assertEqual(len(key_list2.keysets), len(self.full_contact_list))

    def test_load_of_modified_database_raises_critical_error(self) -> None:
        self.keylist.store_keys()

        # Test reading works normally
        self.assertIsInstance(KeyList(self.master_key, self.settings), KeyList)

        # Test loading of the tampered database raises CriticalError
        tamper_file(self.file_name, tamper_size=1)
        with self.assertRaises(SystemExit):
            KeyList(self.master_key, self.settings)

    def test_invalid_content_raises_critical_error(self) -> None:
        # Setup
        invalid_data = b'a'
        pt_bytes = b''.join([
            k.serialize_k()
            for k in self.keylist.keysets + self.keylist._dummy_keysets()
        ])
        ct_bytes = encrypt_and_sign(pt_bytes + invalid_data,
                                    self.master_key.master_key)

        ensure_dir(DIR_USER_DATA)
        with open(self.file_name, 'wb+') as f:
            f.write(ct_bytes)

        # Test
        with self.assertRaises(SystemExit):
            KeyList(self.master_key, self.settings)

    def test_generate_dummy_keyset(self) -> None:
        dummy_keyset = self.keylist.generate_dummy_keyset()
        self.assertEqual(len(dummy_keyset.serialize_k()), KEYSET_LENGTH)
        self.assertIsInstance(dummy_keyset, KeySet)

    def test_dummy_keysets(self) -> None:
        dummies = self.keylist._dummy_keysets()
        self.assertEqual(len(dummies),
                         (self.settings.max_number_of_contacts + 1) -
                         len(self.full_contact_list))
        for c in dummies:
            self.assertIsInstance(c, KeySet)

    def test_add_keyset(self) -> None:
        new_key = bytes(SYMMETRIC_KEY_LENGTH)
        self.keylist.keysets = [create_keyset(LOCAL_ID)]

        # Check that KeySet exists and that its keys are different from the new ones
        self.assertNotEqual(self.keylist.keysets[0].rx_hk, new_key)

        # Replace the existing KeySet
        self.assertIsNone(
            self.keylist.add_keyset(LOCAL_PUBKEY, new_key, new_key, new_key,
                                    new_key))

        # Check that the new KeySet replaced the old one
        self.assertEqual(self.keylist.keysets[0].onion_pub_key, LOCAL_PUBKEY)
        self.assertEqual(self.keylist.keysets[0].rx_hk, new_key)

    def test_remove_keyset(self) -> None:
        # Test that the KeySet for Bob exists
        self.assertTrue(self.keylist.has_keyset(nick_to_pub_key('Bob')))

        # Remove the KeySet for Bob
        self.assertIsNone(self.keylist.remove_keyset(nick_to_pub_key('Bob')))

        # Test that the KeySet was removed
        self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('Bob')))

    @mock.patch('builtins.input', side_effect=['test_password'])
    def test_change_master_key(self, _: Any) -> None:
        # Setup
        key = SYMMETRIC_KEY_LENGTH * b'\x01'
        master_key2 = MasterKey(master_key=key)
        queues = gen_queue_dict()

        def queue_delayer() -> None:
            """Place packet to the key management queue after timer runs out."""
            time.sleep(0.1)
            queues[KEY_MANAGEMENT_QUEUE].put(master_key2.master_key)

        threading.Thread(target=queue_delayer).start()

        # Test that the new key is different from the existing one
        self.assertNotEqual(key, self.master_key.master_key)

        # Change the master key
        self.assertIsNone(self.keylist.change_master_key(queues))

        # Test that the master key was changed
        self.assertEqual(self.keylist.master_key.master_key, key)
        self.assertEqual(self.keylist.database.database_key, key)

        self.assertEqual(queues[KEY_MGMT_ACK_QUEUE].get(), KDB_HALT_ACK_HEADER)
        self.assertEqual(queues[KEY_MGMT_ACK_QUEUE].get(), key)

    def test_update_database(self) -> None:
        # Setup
        queues = gen_queue_dict()

        # Test
        self.assertEqual(os.path.getsize(self.file_name), 9016)
        self.assertIsNone(
            self.keylist.manage(queues, KDB_UPDATE_SIZE_HEADER,
                                Settings(max_number_of_contacts=100)))
        self.assertEqual(os.path.getsize(self.file_name), 17816)
        self.assertEqual(self.keylist.settings.max_number_of_contacts, 100)

    def test_get_keyset(self) -> None:
        keyset = self.keylist.get_keyset(nick_to_pub_key('Alice'))
        self.assertIsInstance(keyset, KeySet)

    def test_get_list_of_pub_keys(self) -> None:
        self.assertEqual(self.keylist.get_list_of_pub_keys(), [
            nick_to_pub_key("Alice"),
            nick_to_pub_key("Bob"),
            nick_to_pub_key("Charlie")
        ])

    def test_has_keyset(self) -> None:
        self.keylist.keysets = []
        self.assertFalse(self.keylist.has_keyset(nick_to_pub_key("Alice")))

        self.keylist.keysets = [create_keyset('Alice')]
        self.assertTrue(self.keylist.has_keyset(nick_to_pub_key("Alice")))

    def test_has_rx_mk(self) -> None:
        self.assertTrue(self.keylist.has_rx_mk(nick_to_pub_key('Bob')))
        self.keylist.get_keyset(
            nick_to_pub_key('Bob')).rx_mk = bytes(SYMMETRIC_KEY_LENGTH)
        self.keylist.get_keyset(
            nick_to_pub_key('Bob')).rx_hk = bytes(SYMMETRIC_KEY_LENGTH)
        self.assertFalse(self.keylist.has_rx_mk(nick_to_pub_key('Bob')))

    def test_has_local_keyset(self) -> None:
        self.keylist.keysets = []
        self.assertFalse(self.keylist.has_local_keyset())

        self.assertIsNone(
            self.keylist.add_keyset(LOCAL_PUBKEY, bytes(SYMMETRIC_KEY_LENGTH),
                                    bytes(SYMMETRIC_KEY_LENGTH),
                                    bytes(SYMMETRIC_KEY_LENGTH),
                                    bytes(SYMMETRIC_KEY_LENGTH)))
        self.assertTrue(self.keylist.has_local_keyset())

    def test_manage(self) -> None:
        # Setup
        queues = gen_queue_dict()

        # Test that the KeySet for David does not exist
        self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('David')))

        # Test adding the KeySet for David
        self.assertIsNone(
            self.keylist.manage(queues, KDB_ADD_ENTRY_HEADER,
                                nick_to_pub_key('David'),
                                bytes(SYMMETRIC_KEY_LENGTH),
                                bytes(SYMMETRIC_KEY_LENGTH),
                                bytes(SYMMETRIC_KEY_LENGTH),
                                bytes(SYMMETRIC_KEY_LENGTH)))
        self.assertTrue(self.keylist.has_keyset(nick_to_pub_key('David')))

        # Test removing David's KeySet
        self.assertIsNone(
            self.keylist.manage(queues, KDB_REMOVE_ENTRY_HEADER,
                                nick_to_pub_key('David')))
        self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('David')))

        # Test changing the master key
        new_key = SYMMETRIC_KEY_LENGTH * b'\x01'

        self.assertNotEqual(self.master_key.master_key, new_key)

        queues[KEY_MANAGEMENT_QUEUE].put(new_key)
        self.assertIsNone(
            self.keylist.manage(queues, KDB_M_KEY_CHANGE_HALT_HEADER))

        self.assertEqual(self.keylist.master_key.master_key, new_key)
        self.assertEqual(self.keylist.database.database_key, new_key)

        # Test an invalid KeyList management command raises CriticalError
        with self.assertRaises(SystemExit):
            self.keylist.manage(queues, 'invalid_key', None)