This commit is contained in:
Markus Ottela 2020-03-06 11:21:13 +02:00
parent 0202e4faa0
commit 0ac650c2fe
87 changed files with 2058 additions and 1194 deletions

0
.coveragerc Normal file → Executable file
View File

0
.travis.yml Normal file → Executable file
View File

View File

@ -80,6 +80,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
MIT License MIT License
applies to: applies to:
- The appdirs library, Copyright (c) 2010 ActiveState Software Inc.
(https://github.com/ActiveState/appdirs)
- The Argon2 library, Copyright © 2015, Hynek Schlawack - The Argon2 library, Copyright © 2015, Hynek Schlawack
(https://github.com/hynek/argon2_cffi) (https://github.com/hynek/argon2_cffi)
@ -119,6 +122,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright © 2011-2016, The virtualenv developers Copyright © 2011-2016, The virtualenv developers
(https://github.com/pypa/virtualenv) (https://github.com/pypa/virtualenv)
- The zipp library. Copyright © Jason R. Coombs
(https://github.com/jaraco/zipp)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -1210,6 +1216,9 @@ Public License instead of this License. But first, please read
https://www.apache.org/licenses/ https://www.apache.org/licenses/
applies to: applies to:
- The importlib_metadata library, Copyright © 2017-2019 Jason R. Coombs, Barry Warsaw
(https://gitlab.com/python-devs/importlib_metadata)
- The OpenSSL library, Copyright © 1995-1998, Eric A. Young, Tim J. Hudson - The OpenSSL library, Copyright © 1995-1998, Eric A. Young, Tim J. Hudson
Copyright © 1999-2018, The OpenSSL Project Copyright © 1999-2018, The OpenSSL Project
(https://github.com/openssl/openssl) (https://github.com/openssl/openssl)
@ -2469,6 +2478,7 @@ Library.
Copyright © Guido van Rossum <guido@cwi.nl> and others. Copyright © Guido van Rossum <guido@cwi.nl> and others.
applies to: applies to:
- The CPython3.7 programming language, Copyright © 1991-1995, Stichting Mathematisch Centrum, Amsterdam. - The CPython3.7 programming language, Copyright © 1991-1995, Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved. All Rights Reserved.
@ -2482,6 +2492,9 @@ Library.
All Rights Reserved. All Rights Reserved.
(https://www.python.org/) (https://www.python.org/)
- distlib library Copyright © 2020 [Python Packaging Authority Developers]
(https://bitbucket.org/pypa/distlib/src/master/)
- The python3-tk library, Copyright © 2006, Matthias Klose - The python3-tk library, Copyright © 2006, Matthias Klose
This package was debianized by Matthias Klose <doko@debian.org> on This package was debianized by Matthias Klose <doko@debian.org> on
Wed, 7 Jun 2006 15:02:31 +0200. Wed, 7 Jun 2006 15:02:31 +0200.
@ -2909,6 +2922,9 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Note: This unlicense is meant only as a description Note: This unlicense is meant only as a description
applies to: applies to:
- The py-filelock libary by Benedikt Schmitt
(https://github.com/benediktschmitt/py-filelock)
- The Reed-Solomon erasure code library by Tomer Filiba, Stephen Larroque - The Reed-Solomon erasure code library by Tomer Filiba, Stephen Larroque
(https://github.com/lrq3000/reedsolomon/) (https://github.com/lrq3000/reedsolomon/)
(https://github.com/tomerfiliba/reedsolomon) (https://github.com/tomerfiliba/reedsolomon)

View File

@ -94,15 +94,16 @@ TFC is designed to be used in hardware configuration that provides strong
[endpoint security](https://en.wikipedia.org/wiki/Endpoint_security). [endpoint security](https://en.wikipedia.org/wiki/Endpoint_security).
This configuration uses three computers per endpoint: Encryption and decryption processes This configuration uses three computers per endpoint: Encryption and decryption processes
are separated from each other onto two isolated computers, the Source Computer, and the are separated from each other onto two isolated computers, the Source Computer, and the
Destination Computer. These two systems are are dedicated for TFC. This split Destination Computer. These two devices are are dedicated for TFC. This split
[TCB](https://en.wikipedia.org/wiki/Trusted_computing_base) [TCB](https://en.wikipedia.org/wiki/Trusted_computing_base)
interacts with the network via the user's daily computer, called the Networked Computer. interacts with the network via the user's daily computer, called the Networked Computer.
Data moves from the Source Computer to the Networked Computer, and from the Networked In TFC data moves from the Source Computer to the Networked Computer, and from the Networked
Computer to the Destination Computer, unidirectionally. The unidirectionality of data flow Computer to the Destination Computer, unidirectionally. The unidirectionality of data
is enforced with a free hardware design flow is enforced, as the data is passed from one device to another only through a free
hardware design
[data diode](https://en.wikipedia.org/wiki/Unidirectional_network), [data diode](https://en.wikipedia.org/wiki/Unidirectional_network),
which is connected to the three computers using one USB-cable per computer. that is connected to the three computers using one USB-cable per device.
The Source and Destination Computers are not connected to the Internet, or to any device The Source and Destination Computers are not connected to the Internet, or to any device
other than the data diode. other than the data diode.
@ -114,8 +115,12 @@ Optical repeater inside the
[optocouplers](https://en.wikipedia.org/wiki/Opto-isolator) [optocouplers](https://en.wikipedia.org/wiki/Opto-isolator)
of the data diode enforce direction of data transmission with the fundamental laws of of the data diode enforce direction of data transmission with the fundamental laws of
physics. This protection is so strong, the certified implementations of data diodes are physics. This protection is so strong, the certified implementations of data diodes are
typically found in critical infrastructure protection and government networks where typically found in critical infrastructure protection and government networks where the
classification level of data varies between systems. classification level of data varies between systems. A data diode might e.g. allow access
to a nuclear power plant's safety system readings, while at the same time preventing
attackers from exploiting these critical systems. An alternative use case is to allow
importing data from less secure systems to ones that contain classified documents that
must be protected from exfiltration.
In TFC the hardware data diode ensures that neither of the TCB-halves can be accessed In TFC the hardware data diode ensures that neither of the TCB-halves can be accessed
bidirectionally. Since the protection relies on physical limitations of the hardware's bidirectionally. Since the protection relies on physical limitations of the hardware's
@ -183,18 +188,39 @@ the Source or Destination Computer, the ciphertexts are of no value to the attac
[Exfiltration security](https://www.cs.helsinki.fi/u/oottela/wiki/readme/attacks.png) [Exfiltration security](https://www.cs.helsinki.fi/u/oottela/wiki/readme/attacks.png)
### Qubes-isolated intermediate solution
For some users the
[APTs](https://en.wikipedia.org/wiki/Advanced_persistent_threat)
of the modern world are not part of the threat model, and for others, the
requirement of having to build the data diode by themselves is a deal breaker. Yet, for
all of them, storing private keys on a networked device is still a security risk.
To meet these users' needs, TFC can also be run in three dedicated
[Qubes](https://www.qubes-os.org/)
virtual machines. With the Qubes configuration, the isolation is provided by the
[Xen hypervisor](https://xenproject.org/users/security/),
and the unidirectionality of data flow between the VMs is enforced with strict firewall
rules. This intermediate isolation mechanism runs on a single computer which means no
hardware data diode is needed.
### Supported Operating Systems ### Supported Operating Systems
#### Source/Destination Computer #### Source/Destination Computer
- Debian 10 - Debian 10
- PureOS 9.0 - PureOS 9.0
- *buntu 19.10 - *buntu 19.10
- LMDE 4
- Qubes 4 (Debian 10 VM)
#### Networked Computer #### Networked Computer
- Tails 4.0 - Tails 4.0
- Debian 10 - Debian 10
- PureOS 9.0 - PureOS 9.0
- *buntu 19.10 - *buntu 19.10
- LMDE 4
- Qubes 4 (Debian 10 VM)
### More information ### More information
@ -209,10 +235,9 @@ Hardware Data Diode<Br>
How to use<br> How to use<br>
&nbsp;&nbsp;&nbsp;&nbsp;[Installation](https://github.com/maqp/tfc/wiki/Installation)<br> &nbsp;&nbsp;&nbsp;&nbsp;[Installation](https://github.com/maqp/tfc/wiki/Installation)<br>
&nbsp;&nbsp;&nbsp;&nbsp;[Launching TFC](https://github.com/maqp/tfc/wiki/Launching-TFC)<br> &nbsp;&nbsp;&nbsp;&nbsp;[Master password setup](https://github.com/maqp/tfc/wiki/Master-Password)<br>
&nbsp;&nbsp;&nbsp;&nbsp;[Setup master password](https://github.com/maqp/tfc/wiki/Master-Password)<br>
&nbsp;&nbsp;&nbsp;&nbsp;[Local key setup](https://github.com/maqp/tfc/wiki/Local-Key-Setup)<br> &nbsp;&nbsp;&nbsp;&nbsp;[Local key setup](https://github.com/maqp/tfc/wiki/Local-Key-Setup)<br>
&nbsp;&nbsp;&nbsp;&nbsp;[Launch Onion Service](https://github.com/maqp/tfc/wiki/Onion-Service-Setup)<br> &nbsp;&nbsp;&nbsp;&nbsp;[Onion Service setup](https://github.com/maqp/tfc/wiki/Onion-Service-Setup)<br>
&nbsp;&nbsp;&nbsp;&nbsp;[X448 key exchange](https://github.com/maqp/tfc/wiki/X448)<br> &nbsp;&nbsp;&nbsp;&nbsp;[X448 key exchange](https://github.com/maqp/tfc/wiki/X448)<br>
&nbsp;&nbsp;&nbsp;&nbsp;[Pre-shared keys](https://github.com/maqp/tfc/wiki/PSK)<br> &nbsp;&nbsp;&nbsp;&nbsp;[Pre-shared keys](https://github.com/maqp/tfc/wiki/PSK)<br>
&nbsp;&nbsp;&nbsp;&nbsp;[Commands](https://github.com/maqp/tfc/wiki/Commands)<br> &nbsp;&nbsp;&nbsp;&nbsp;[Commands](https://github.com/maqp/tfc/wiki/Commands)<br>

1120
install.sh

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,16 @@
-----BEGIN PGP SIGNATURE----- -----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEE3wqdU+qbbuozmTV+rAVyyKvL4QFAl42DPoACgkQ+rAVyyKv iQIzBAABCAAdFiEEE3wqdU+qbbuozmTV+rAVyyKvL4QFAl42GLEACgkQ+rAVyyKv
L4S3Gg/+OujW2IlEDBpxd97jPRRH1L3UZ3tHOV2VuV5hIukkblOLx1UZJbWWL/VC L4SRQA//ccJj0h7tRE9kVu0Txi7BXDBzUQCD7c8yhTqRxoGTTzRj1bHrBDDeP2e/
/Q4Yd9Xi6f58Jwz/f7RIFBzp1xNa6rEcTYT6CBTvzsyxDyrUQQVgGzdJhuYHqoRk pd2c5MQLNFE26pnGxhpvVgEFfSWSxQxxs5BgPhFAj9V1Bn1CrrBJYueuupBo8An3
j6b8SLuxnafEEtVjgESoy0Ei5bSgs9l4aZU/Jd86ClUI0yF4SWeh562UWGHXObVJ VdRzqArBljGgScGOPXECeTwldoWY5ugtxREwQlBL7JCix9wmq4/yghzE61YdN5K3
/RtjrpnKn6OnIVY5QvbYOpTk2Q3dd0sz26/pxykptselzN+2kFCl+4mtu5oT1bkx tx+WVj4Y06SWZAni6nssYiBYrToAslAgTlyAtaCYJccOUpHgnsyfqzZLH7+a/6Lz
+c33lp3ihyJyNpEkEqISudtfR5FfQlq5ZbQRL9p77Y9e4ePUG6wlUN0dlm7oXS2/ NiHTJHm9zsZ6KzzpgnhDNMhTlZK9m2fuwdVMU6JjScNZA9gswTdKyi8kPeVpm/1g
uS1Y1+U1wQNcMjisOo/bZs9wPzatfP9cl9I5DCl1vogMheSYKuORR3kc0FR+cAuX m0LZAxOLcZuMKNdG/Wrtm/174yFIURoOmg7rF8m1FKHwvLQa2+FICICx7CLBiASA
/J0KryZrMP3kK43eM4LzHdoimGyX6D79Wdy2cPZ70QpKYrCDjSawanmX1ZxxPblD Z+vVzfI7py97/hiVTNFDTlKENk4kS9Auhaf5pI6f2v/ehKXYnTYc8sLSUk5MYVWI
HfmHnJ0Inc1o85lf5l/PYy3xLQrQbBuUIlctBFWbpW7XdUqKS9HdVqzxGnbOAJnP 06ZmMJ3cvD4P/NPr7nCDT9WHUx+qKMnSQirQ86/wSxK3KcjE9Fu8Q8AXTYVZSN11
C59O3EpkiMqV3I9zn3e85wMzKy4xrbrm/asl+S97BdHzZf8xGdvBRBwYK1OFfzKL xtCCtDkrd6TbxTwl5K54syoerg9PqkiWnRmf0gi00LuoJExg8i4Td2jBMVpxRJhi
7fqxJDfkkOoTyrC0vhO+mbm2ktyR1oOjRCsEitXWA1sYmz1x+NbuaMQoiwpnefFG KGGIj2GhexiB/slyz2kEZsmIkZr+dMqHTxoQwSoop9Ev0GHjkgkGa10LxxoRAzUg
JG1EYzokYahZFTz3NrfG2IK+2vOr7TV6KlWasQLKboleiMNQEZw= x0A1+8TJo1dOs8+GD5qN6N68ZyMQhlAmp5b2EED0lrbQVRkZEig=
=v3mk =/l6F
-----END PGP SIGNATURE----- -----END PGP SIGNATURE-----

View File

@ -1,5 +1,5 @@
[Desktop Entry] [Desktop Entry]
Version=1.20.02 Version=1.20.03
Name=TFC-Dev-LR Name=TFC-Dev-LR
Comment=Developer configuration Comment=Developer configuration
Exec=terminator -m -u -g $HOME/tfc/launchers/terminator-config-dev -p tfc -l tfc-lr Exec=terminator -m -u -g $HOME/tfc/launchers/terminator-config-dev -p tfc -l tfc-lr

View File

@ -1,5 +1,5 @@
[Desktop Entry] [Desktop Entry]
Version=1.20.02 Version=1.20.03
Name=TFC-Local-Test-LR Name=TFC-Local-Test-LR
Comment=Local testing configuration Comment=Local testing configuration
Exec=terminator -m -u -g /opt/tfc/terminator-config-local-test -p tfc -l tfc-lr Exec=terminator -m -u -g /opt/tfc/terminator-config-local-test -p tfc -l tfc-lr

8
launchers/TFC-RP-Qubes.desktop Executable file
View File

@ -0,0 +1,8 @@
[Desktop Entry]
Version=1.20.03
Name=TFC-Relay
Exec=gnome-terminal --geometry=94x25 -x bash -c "source /opt/tfc/venv_relay/bin/activate && python3.7 /opt/tfc/relay.py -q && deactivate || bash"
Icon=tfc.png
Terminal=false
Type=Application
Categories=Network;Messaging;Security;

View File

@ -1,7 +1,7 @@
[Desktop Entry] [Desktop Entry]
Version=1.20.02 Version=1.20.03
Name=TFC-Relay Name=TFC-Relay
Exec=gnome-terminal -x bash -c "cd /opt/tfc && source venv_relay/bin/activate && python3.7 'relay.py' && deactivate || bash" Exec=gnome-terminal --geometry=105x25 -x bash -c "cd /opt/tfc && source venv_relay/bin/activate && python3.7 'relay.py' && deactivate || bash"
Icon=tfc.png Icon=tfc.png
Terminal=false Terminal=false
Type=Application Type=Application

View File

@ -1,7 +1,7 @@
[Desktop Entry] [Desktop Entry]
Version=1.20.02 Version=1.20.03
Name=TFC-Relay Name=TFC-Relay
Exec=gnome-terminal -x bash -c "cd /opt/tfc && source venv_relay/bin/activate && python3.7 'relay.py' && deactivate || bash" Exec=gnome-terminal --geometry=105x25 -x bash -c "cd /opt/tfc && source venv_relay/bin/activate && python3.7 'relay.py' && deactivate || bash"
Icon=tfc.png Icon=tfc.png
Terminal=false Terminal=false
Type=Application Type=Application

View File

@ -0,0 +1,8 @@
[Desktop Entry]
Version=1.20.03
Name=TFC-Receiver
Exec=gnome-terminal --geometry=94x25 -x bash -c "source /opt/tfc/venv_tcb/bin/activate && python3.7 /opt/tfc/tfc.py -r -q && deactivate || bash"
Icon=tfc.png
Terminal=false
Type=Application
Categories=Network;Messaging;Security;

View File

@ -1,5 +1,5 @@
[Desktop Entry] [Desktop Entry]
Version=1.20.02 Version=1.20.03
Name=TFC-Receiver Name=TFC-Receiver
Exec=gnome-terminal --maximize -x bash -c "cd /opt/tfc && source venv_tcb/bin/activate && python3.7 'tfc.py' -r && deactivate || bash" Exec=gnome-terminal --maximize -x bash -c "cd /opt/tfc && source venv_tcb/bin/activate && python3.7 'tfc.py' -r && deactivate || bash"
Icon=tfc.png Icon=tfc.png

View File

@ -0,0 +1,8 @@
[Desktop Entry]
Version=1.20.03
Name=TFC-Transmitter
Exec=gnome-terminal --geometry=94x25 -x bash -c "source /opt/tfc/venv_tcb/bin/activate && python3.7 /opt/tfc/tfc.py -q && deactivate || bash"
Icon=tfc.png
Terminal=false
Type=Application
Categories=Network;Messaging;Security;

View File

@ -1,5 +1,5 @@
[Desktop Entry] [Desktop Entry]
Version=1.20.02 Version=1.20.03
Name=TFC-Transmitter Name=TFC-Transmitter
Exec=gnome-terminal --maximize -x bash -c "cd /opt/tfc && source venv_tcb/bin/activate && python3.7 'tfc.py' && deactivate || bash" Exec=gnome-terminal --maximize -x bash -c "cd /opt/tfc && source venv_tcb/bin/activate && python3.7 'tfc.py' && deactivate || bash"
Icon=tfc.png Icon=tfc.png

19
launchers/tfc-qubes-receiver Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
# TFC - Onion-routed, endpoint secure messaging system
# Copyright (C) 2013-2020 Markus Ottela
#
# This file is part of TFC.
#
# TFC is free software: you can redistribute it and/or modify it under the terms
# of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
#
# TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with TFC. If not, see <https://www.gnu.org/licenses/>.
cd /opt/tfc/ && source venv_tcb/bin/activate && python3.7 tfc.py -q -r

19
launchers/tfc-qubes-relay Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
# TFC - Onion-routed, endpoint secure messaging system
# Copyright (C) 2013-2020 Markus Ottela
#
# This file is part of TFC.
#
# TFC is free software: you can redistribute it and/or modify it under the terms
# of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
#
# TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with TFC. If not, see <https://www.gnu.org/licenses/>.
cd /opt/tfc/ && source venv_relay/bin/activate && python3.7 relay.py -q

19
launchers/tfc-qubes-transmitter Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
# TFC - Onion-routed, endpoint secure messaging system
# Copyright (C) 2013-2020 Markus Ottela
#
# This file is part of TFC.
#
# TFC is free software: you can redistribute it and/or modify it under the terms
# of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
#
# TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with TFC. If not, see <https://www.gnu.org/licenses/>.
cd /opt/tfc/ && source venv_tcb/bin/activate && python3.7 tfc.py -q

View File

@ -139,9 +139,9 @@ def main() -> None:
ensure_dir(working_dir) ensure_dir(working_dir)
os.chdir(working_dir) os.chdir(working_dir)
_, local_test, data_diode_sockets = process_arguments() _, local_test, data_diode_sockets, qubes = process_arguments()
gateway = Gateway(NC, local_test, data_diode_sockets) gateway = Gateway(NC, local_test, data_diode_sockets, qubes)
print_title(NC) print_title(NC)
@ -167,7 +167,7 @@ def main() -> None:
ONION_KEY_QUEUE: Queue(), # Onion Service private key from `relay_command` to `onion_service` ONION_KEY_QUEUE: Queue(), # Onion Service private key from `relay_command` to `onion_service`
TOR_DATA_QUEUE: Queue(), # Open port for Tor from `onion_service` to `client_scheduler` TOR_DATA_QUEUE: Queue(), # Open port for Tor from `onion_service` to `client_scheduler`
EXIT_QUEUE: Queue(), # EXIT/WIPE signal from `relay_command` to `main` EXIT_QUEUE: Queue(), # EXIT/WIPE signal from `relay_command` to `main`
ACCOUNT_CHECK_QUEUE: Queue(), # Incorrectly typed accounts from `src_incomfing` to `account_checker` ACCOUNT_CHECK_QUEUE: Queue(), # Incorrectly typed accounts from `src_incoming` to `account_checker`
ACCOUNT_SEND_QUEUE: Queue(), # Contact requests from `flask_server` to `account_checker` ACCOUNT_SEND_QUEUE: Queue(), # Contact requests from `flask_server` to `account_checker`
USER_ACCOUNT_QUEUE: Queue(), # User's public key from `onion_service` to `account_checker` USER_ACCOUNT_QUEUE: Queue(), # User's public key from `onion_service` to `account_checker`
PUB_KEY_CHECK_QUEUE: Queue(), # Typed public keys from `src_incoming` to `pub_key_checker` PUB_KEY_CHECK_QUEUE: Queue(), # Typed public keys from `src_incoming` to `pub_key_checker`
@ -184,7 +184,7 @@ def main() -> None:
Process(target=flask_server, args=(queues, url_token_public_key )), Process(target=flask_server, args=(queues, url_token_public_key )),
Process(target=onion_service, args=(queues, )), Process(target=onion_service, args=(queues, )),
Process(target=relay_command, args=(queues, gateway, )), Process(target=relay_command, args=(queues, gateway, )),
Process(target=account_checker, args=(queues, sys.stdin.fileno())), Process(target=account_checker, args=(queues, sys.stdin.fileno() )),
Process(target=pub_key_checker, args=(queues, local_test ))] Process(target=pub_key_checker, args=(queues, local_test ))]
for p in process_list: for p in process_list:

View File

@ -1,57 +1,72 @@
# Static type checking tool # argon2_cffi
six>=1.14.0
pycparser>=2.20
cffi>=1.14.0
argon2_cffi>=19.2.0
# cryptography (pyca)
cryptography>=2.8
# Flask
Werkzeug>=1.0.0
MarkupSafe>=1.1.1
Jinja2>=2.11.1
itsdangerous>=1.1.0
click>=7.0
Flask>=1.1.1
# mypy static type checking tool
typing-extensions>=3.7.4.1
typed-ast>=1.4.1
mypy-extensions>=0.4.3
mypy>=0.761 mypy>=0.761
mypy_extensions>=0.4.3
typed_ast>=1.4.1
typing_extensions>=3.7.4.1
# Unit test tools # PyLama
pytest>=5.3.5 pyflakes>=2.1.1
pytest-cov>=2.8.1 snowballstemmer>=2.0.0
pytest-xdist>=1.31.0 pydocstyle>=5.0.2
pycodestyle>=2.5.0
mccabe>=0.6.1
pylama>=7.7.1
# TFC dependencies (note: not authenticated with hashes) # PyNaCl (pyca)
setuptools>=45.2.0
PyNaCl>=1.3.0
# pyserial # pyserial
pyserial>=3.4 pyserial>=3.4
# argon2_cffi # PySocks
argon2_cffi>=19.2.0 PySocks>=1.7.1
cffi>=1.13.2
pycparser>=2.19
six>=1.14.0
# pyca/pynacl # pytest
PyNaCl>=1.3.0 wcwidth>=0.1.8
setuptools>=45.1.0 py>=1.8.1
pluggy>=0.13.1
pyparsing>=2.4.6
packaging>=20.3
more-itertools>=8.2.0
zipp>=3.1.0
importlib-metadata>=1.5.0
attrs>=19.3.0
pytest>=5.3.5
# pyca/cryptography # pytest-cov
cryptography>=2.8 coverage>=5.0.3
pytest-cov>=2.8.1
# xdist: pytest distributed testing plugin
pytest-forked>=1.1.3
apipkg>=1.5
execnet>=1.7.1
pytest-xdist>=1.31.0
# Requests
requests>=2.23.0
certifi>=2019.11.28
chardet>=3.0.4
idna>=2.9
urllib3>=1.25.8
# Stem # Stem
stem>=1.8.0 stem>=1.8.0
# PySocks
pysocks>=1.7.1
# Requests
requests>=2.22.0
certifi>=2019.11.28
chardet>=3.0.4
idna>=2.8
urllib3>=1.25.8
# Flask
flask>=1.1.1
click>=7.0
itsdangerous>=1.1.0
jinja2>=2.11.1
markupsafe>=1.1.1
werkzeug>=0.16.1
# PyLama
pylama>=7.7.1
snowballstemmer>=2.0.0
pyflakes>=2.1.1
pydocstyle>=5.0.2
pycodestyle>=2.5.0
mccabe>=0.6.1

View File

@ -4,16 +4,16 @@
pyserial==3.4 --hash=sha512:8333ac2843fd136d5d0d63b527b37866f7d18afc3bb33c4938b63af077492aeb118eb32a89ac78547f14d59a2adb1e5d00728728275de62317da48dadf6cdff9 pyserial==3.4 --hash=sha512:8333ac2843fd136d5d0d63b527b37866f7d18afc3bb33c4938b63af077492aeb118eb32a89ac78547f14d59a2adb1e5d00728728275de62317da48dadf6cdff9
# Stem (Connects to Tor and manages Onion Services) # Stem (Connects to Tor and manages Onion Services)
stem==1.8.0 --hash=sha512:aa2033567b79aef960f8321e4c6cbc28105c59d6513ff49a9f12509d8f97b1a2e8a3b04dc28abb07fad59b0f6ba66443b92bbefa0d08b26038bbaf24f7f2846d # stem==1.8.0 --hash=sha512:aa2033567b79aef960f8321e4c6cbc28105c59d6513ff49a9f12509d8f97b1a2e8a3b04dc28abb07fad59b0f6ba66443b92bbefa0d08b26038bbaf24f7f2846d
# PySocks (Routes requests library through SOCKS5 proxy making Onion Service connections possible) # PySocks (Routes requests library through SOCKS5 proxy making Onion Service connections possible)
pysocks==1.7.1 --hash=sha512:313b954102231d038d52ab58f41e3642579be29f827135b8dd92c06acb362effcb0a7fd5f35de9273372b92d9fe29f38381ae44f8b41aa90d2564d6dd07ecd12 pysocks==1.7.1 --hash=sha512:313b954102231d038d52ab58f41e3642579be29f827135b8dd92c06acb362effcb0a7fd5f35de9273372b92d9fe29f38381ae44f8b41aa90d2564d6dd07ecd12
# Requests (Connects to the contact's Tor Onion Service) # Requests (Connects to the contact's Tor Onion Service)
requests==2.22.0 --hash=sha512:9186ce4e39bb64f5931a205ffc9afac61657bc42078bc4754ed12a2b66a12b7a620583440849fc2e161d1061ac0750ddef4670f54916931ace1e9abd2a9fb09c requests==2.23.0 --hash=sha512:98e4c9435434b8f63fc37a21133adbbfeb471bfb8b40d60f04bded5cbe328c14a22527d54ab2a55a81d93110d627bacc26943e55ec338b7bed8708b55e15fff3
certifi==2019.11.28 --hash=sha512:fe5b05c29c1e1d9079150aaea28b09d84f0dd15907e276ccabb314433cfaac948a9615e10d6d01cbd537f99eed8072fbda7cb901e932fbab4f1286ae8c50471b certifi==2019.11.28 --hash=sha512:fe5b05c29c1e1d9079150aaea28b09d84f0dd15907e276ccabb314433cfaac948a9615e10d6d01cbd537f99eed8072fbda7cb901e932fbab4f1286ae8c50471b
chardet==3.0.4 --hash=sha512:bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4 chardet==3.0.4 --hash=sha512:bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4
idna==2.8 --hash=sha512:fb07dbec1de86efbad82a4f73d98123c59b083c1f1277445204bef75de99ca200377ad2f1db8924ae79b31b3dd984891c87d0a6344ec4d07a0ddbbbc655821a3 idna==2.9 --hash=sha512:be96b782728404acec374f446b11811f8e76d5ed42d4673a07e883220f5ba2a099a8124cda5898c3f5da7d92b87b36127e8fd42e9edb240b587a380ed73cce93
urllib3==1.25.8 --hash=sha512:f7fd3b54b7c555c0e74eb445e543763d233b5c6f8021ccf46a45d452c334953276d43ecd8f3d0eafefa35103a7d1874e291216fc9a41362eb6f1250a2a670f16 urllib3==1.25.8 --hash=sha512:f7fd3b54b7c555c0e74eb445e543763d233b5c6f8021ccf46a45d452c334953276d43ecd8f3d0eafefa35103a7d1874e291216fc9a41362eb6f1250a2a670f16
# Flask (Onion Service web server that serves TFC public keys and ciphertexts to contacts) # Flask (Onion Service web server that serves TFC public keys and ciphertexts to contacts)
@ -22,12 +22,13 @@ click==7.0 --hash=sha512:6b30987349df7c45c5f41cff9076ed45b178b444fca1ab
itsdangerous==1.1.0 --hash=sha512:891c294867f705eb9c66274bd04ac5d93140d6e9beea6cbf9a44e7f9c13c0e2efa3554bdf56620712759a5cd579e112a782d25f3f91ba9419d60b2b4d2bc5b7c itsdangerous==1.1.0 --hash=sha512:891c294867f705eb9c66274bd04ac5d93140d6e9beea6cbf9a44e7f9c13c0e2efa3554bdf56620712759a5cd579e112a782d25f3f91ba9419d60b2b4d2bc5b7c
jinja2==2.11.1 --hash=sha512:461bbd517560f1c4dbf7309bdf0cf33b468938fddfa2c3385fab07343269732d8ce68d8827148645113267d48e7d67b03f1663cc64839dd1fcec723ea606aaf4 jinja2==2.11.1 --hash=sha512:461bbd517560f1c4dbf7309bdf0cf33b468938fddfa2c3385fab07343269732d8ce68d8827148645113267d48e7d67b03f1663cc64839dd1fcec723ea606aaf4
markupsafe==1.1.1 --hash=sha512:69e9b9c9ac4fdf3cfa1a3de23d14964b843989128f8cc6ea58617fc5d6ef937bcc3eae9cb32b5164b5f54b06f96bdff9bc249529f20671cc26adc9e6ce8f6bec markupsafe==1.1.1 --hash=sha512:69e9b9c9ac4fdf3cfa1a3de23d14964b843989128f8cc6ea58617fc5d6ef937bcc3eae9cb32b5164b5f54b06f96bdff9bc249529f20671cc26adc9e6ce8f6bec
werkzeug==0.16.1 --hash=sha512:4c982970fef39bf7cfbb4e516864fec0f8ec3f743ccb632d1659c6ee415597d98f4abd63b5c0fd999eb43fc0c89a97123f07625b01ea86b02ef51cb67a2b148d werkzeug==1.0.0 --hash=sha512:82a0f1776820d07e929daa60bfa0a3e746464b0f2923376330f8ae5abf535bcb756c7384757b2ff8e0076f299fe85d96ef34b3a8eede21c11df9aba8cc58cb77
# Cryptography (Handles URL token derivation) # Cryptography (Handles URL token derivation)
cryptography==2.8 --hash=sha512:184003c89fee74892de25c3e5ec366faea7a5f1fcca3c82b0d5e5f9f797286671a820ca54da5266d6f879ab342c97e25bce9db366c5fb1178690cd5978d4d622 cryptography==2.8 --hash=sha512:184003c89fee74892de25c3e5ec366faea7a5f1fcca3c82b0d5e5f9f797286671a820ca54da5266d6f879ab342c97e25bce9db366c5fb1178690cd5978d4d622 \
cffi==1.13.2 --hash=sha512:b8753a0435cc7a2176f8748badc074ec6ffab6698d6be42b1770c85871f85aa7cf60152a8be053c3031b234a286c5cef07267cb812accb704783d74a2675ed3b --hash=sha512:d8ddabe127ae8d7330d219e284de68b37fa450a27b4cf05334e9115388295b00148d9861c23b1a2e5ea9df0c33a2d27f3e4b25ce9abd3c334f1979920b19c902
pycparser==2.19 --hash=sha512:7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5 cffi==1.14.0 --hash=sha512:5b315a65fc8f40622ceef35466546620aaca9dd304f5491a845239659b4066469c5fb3f1683c382eb57f8975caf318e5d88852e3dbb049cde193c9189b88c9c0
pycparser==2.20 --hash=sha512:06dc9cefdcde6b97c96d0452a77db42a629c48ee545edd7ab241763e50e3b3c56d21f9fcce4e206817aa1a597763d948a10ccc73572490d739c89eea7fede0a1
six==1.14.0 --hash=sha512:a6e7e35921ce8f2f8e79a296ea79a9c3515ff6dd7e777d7892fe4988594f1b3a442a68ffb89cf64530b90a32ceeea00e4ab9069bb697629ab4eb7262c68d1b0f six==1.14.0 --hash=sha512:a6e7e35921ce8f2f8e79a296ea79a9c3515ff6dd7e777d7892fe4988594f1b3a442a68ffb89cf64530b90a32ceeea00e4ab9069bb697629ab4eb7262c68d1b0f
# PyNaCl (Derives TFC account from Onion Service private key) # PyNaCl (Derives TFC account from Onion Service private key)

View File

@ -10,10 +10,10 @@ stem==1.8.0 --hash=sha512:aa2033567b79aef960f8321e4c6cbc28105c59d6513ff4
pysocks==1.7.1 --hash=sha512:313b954102231d038d52ab58f41e3642579be29f827135b8dd92c06acb362effcb0a7fd5f35de9273372b92d9fe29f38381ae44f8b41aa90d2564d6dd07ecd12 pysocks==1.7.1 --hash=sha512:313b954102231d038d52ab58f41e3642579be29f827135b8dd92c06acb362effcb0a7fd5f35de9273372b92d9fe29f38381ae44f8b41aa90d2564d6dd07ecd12
# Requests (Connects to the contact's Tor Onion Service) # Requests (Connects to the contact's Tor Onion Service)
requests==2.22.0 --hash=sha512:9186ce4e39bb64f5931a205ffc9afac61657bc42078bc4754ed12a2b66a12b7a620583440849fc2e161d1061ac0750ddef4670f54916931ace1e9abd2a9fb09c requests==2.23.0 --hash=sha512:98e4c9435434b8f63fc37a21133adbbfeb471bfb8b40d60f04bded5cbe328c14a22527d54ab2a55a81d93110d627bacc26943e55ec338b7bed8708b55e15fff3
certifi==2019.11.28 --hash=sha512:fe5b05c29c1e1d9079150aaea28b09d84f0dd15907e276ccabb314433cfaac948a9615e10d6d01cbd537f99eed8072fbda7cb901e932fbab4f1286ae8c50471b certifi==2019.11.28 --hash=sha512:fe5b05c29c1e1d9079150aaea28b09d84f0dd15907e276ccabb314433cfaac948a9615e10d6d01cbd537f99eed8072fbda7cb901e932fbab4f1286ae8c50471b
chardet==3.0.4 --hash=sha512:bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4 chardet==3.0.4 --hash=sha512:bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4
idna==2.8 --hash=sha512:fb07dbec1de86efbad82a4f73d98123c59b083c1f1277445204bef75de99ca200377ad2f1db8924ae79b31b3dd984891c87d0a6344ec4d07a0ddbbbc655821a3 idna==2.9 --hash=sha512:be96b782728404acec374f446b11811f8e76d5ed42d4673a07e883220f5ba2a099a8124cda5898c3f5da7d92b87b36127e8fd42e9edb240b587a380ed73cce93
urllib3==1.25.8 --hash=sha512:f7fd3b54b7c555c0e74eb445e543763d233b5c6f8021ccf46a45d452c334953276d43ecd8f3d0eafefa35103a7d1874e291216fc9a41362eb6f1250a2a670f16 urllib3==1.25.8 --hash=sha512:f7fd3b54b7c555c0e74eb445e543763d233b5c6f8021ccf46a45d452c334953276d43ecd8f3d0eafefa35103a7d1874e291216fc9a41362eb6f1250a2a670f16
# Flask (Onion Service web server that serves TFC public keys and ciphertexts to contacts) # Flask (Onion Service web server that serves TFC public keys and ciphertexts to contacts)
@ -22,15 +22,16 @@ click==7.0 --hash=sha512:6b30987349df7c45c5f41cff9076ed45b178b444fca1ab
itsdangerous==1.1.0 --hash=sha512:891c294867f705eb9c66274bd04ac5d93140d6e9beea6cbf9a44e7f9c13c0e2efa3554bdf56620712759a5cd579e112a782d25f3f91ba9419d60b2b4d2bc5b7c itsdangerous==1.1.0 --hash=sha512:891c294867f705eb9c66274bd04ac5d93140d6e9beea6cbf9a44e7f9c13c0e2efa3554bdf56620712759a5cd579e112a782d25f3f91ba9419d60b2b4d2bc5b7c
jinja2==2.11.1 --hash=sha512:461bbd517560f1c4dbf7309bdf0cf33b468938fddfa2c3385fab07343269732d8ce68d8827148645113267d48e7d67b03f1663cc64839dd1fcec723ea606aaf4 jinja2==2.11.1 --hash=sha512:461bbd517560f1c4dbf7309bdf0cf33b468938fddfa2c3385fab07343269732d8ce68d8827148645113267d48e7d67b03f1663cc64839dd1fcec723ea606aaf4
markupsafe==1.1.1 --hash=sha512:69e9b9c9ac4fdf3cfa1a3de23d14964b843989128f8cc6ea58617fc5d6ef937bcc3eae9cb32b5164b5f54b06f96bdff9bc249529f20671cc26adc9e6ce8f6bec markupsafe==1.1.1 --hash=sha512:69e9b9c9ac4fdf3cfa1a3de23d14964b843989128f8cc6ea58617fc5d6ef937bcc3eae9cb32b5164b5f54b06f96bdff9bc249529f20671cc26adc9e6ce8f6bec
werkzeug==0.16.1 --hash=sha512:4c982970fef39bf7cfbb4e516864fec0f8ec3f743ccb632d1659c6ee415597d98f4abd63b5c0fd999eb43fc0c89a97123f07625b01ea86b02ef51cb67a2b148d werkzeug==1.0.0 --hash=sha512:82a0f1776820d07e929daa60bfa0a3e746464b0f2923376330f8ae5abf535bcb756c7384757b2ff8e0076f299fe85d96ef34b3a8eede21c11df9aba8cc58cb77
# Cryptography (Handles URL token derivation) # Cryptography (Handles URL token derivation)
cryptography==2.8 --hash=sha512:184003c89fee74892de25c3e5ec366faea7a5f1fcca3c82b0d5e5f9f797286671a820ca54da5266d6f879ab342c97e25bce9db366c5fb1178690cd5978d4d622 cryptography==2.8 --hash=sha512:184003c89fee74892de25c3e5ec366faea7a5f1fcca3c82b0d5e5f9f797286671a820ca54da5266d6f879ab342c97e25bce9db366c5fb1178690cd5978d4d622 \
cffi==1.13.2 --hash=sha512:b8753a0435cc7a2176f8748badc074ec6ffab6698d6be42b1770c85871f85aa7cf60152a8be053c3031b234a286c5cef07267cb812accb704783d74a2675ed3b --hash=sha512:d8ddabe127ae8d7330d219e284de68b37fa450a27b4cf05334e9115388295b00148d9861c23b1a2e5ea9df0c33a2d27f3e4b25ce9abd3c334f1979920b19c902
pycparser==2.19 --hash=sha512:7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5 cffi==1.14.0 --hash=sha512:5b315a65fc8f40622ceef35466546620aaca9dd304f5491a845239659b4066469c5fb3f1683c382eb57f8975caf318e5d88852e3dbb049cde193c9189b88c9c0
pycparser==2.20 --hash=sha512:06dc9cefdcde6b97c96d0452a77db42a629c48ee545edd7ab241763e50e3b3c56d21f9fcce4e206817aa1a597763d948a10ccc73572490d739c89eea7fede0a1
six==1.14.0 --hash=sha512:a6e7e35921ce8f2f8e79a296ea79a9c3515ff6dd7e777d7892fe4988594f1b3a442a68ffb89cf64530b90a32ceeea00e4ab9069bb697629ab4eb7262c68d1b0f six==1.14.0 --hash=sha512:a6e7e35921ce8f2f8e79a296ea79a9c3515ff6dd7e777d7892fe4988594f1b3a442a68ffb89cf64530b90a32ceeea00e4ab9069bb697629ab4eb7262c68d1b0f
# PyNaCl (Derives TFC account from Onion Service private key) # PyNaCl (Derives TFC account from Onion Service private key)
PyNaCl==1.3.0 --hash=sha512:c4017c38b026a5c531b15839b8d61d1fae9907ba1960c2f97f4cd67fe0827729346d5186a6d6927ba84f64b4cbfdece12b287aa7750a039f4160831be871cea3 PyNaCl==1.3.0 --hash=sha512:c4017c38b026a5c531b15839b8d61d1fae9907ba1960c2f97f4cd67fe0827729346d5186a6d6927ba84f64b4cbfdece12b287aa7750a039f4160831be871cea3
setuptools==45.1.0 --hash=sha512:761e4c8df239b8d173513b08959b387c1059e3e023ba6b3f6250fade518d6ef29f287ab90dd35d02bb681b410a050b30b2ed44849638b6f98831f4290a4ccd15 setuptools==45.2.0 --hash=sha512:de1ac45cb52e8a28322048e6a2b95015aa6826c49679349a1b579cb46b95cb2ffd62242c861c2fe3e059c0c55d4fdb4384c51b964ca2634b2843263543f8842a
# Duplicate sub-dependencies: cffi, pycparser, six # Duplicate sub-dependencies: cffi, pycparser, six

View File

@ -1,2 +1,2 @@
# Setuptools (Allows installation of pycparser which is a sub-dependency of the cryptography and PyNaCl packages) # Setuptools (Allows installation of pycparser which is a sub-dependency of the cryptography and PyNaCl packages)
setuptools==45.1.0 --hash=sha512:761e4c8df239b8d173513b08959b387c1059e3e023ba6b3f6250fade518d6ef29f287ab90dd35d02bb681b410a050b30b2ed44849638b6f98831f4290a4ccd15 setuptools==45.2.0 --hash=sha512:de1ac45cb52e8a28322048e6a2b95015aa6826c49679349a1b579cb46b95cb2ffd62242c861c2fe3e059c0c55d4fdb4384c51b964ca2634b2843263543f8842a

View File

@ -1,2 +1,12 @@
# Sub-dependencies are listed below dependencies
# Virtual environment (Used to create an isolated Python environment for TFC dependencies) # Virtual environment (Used to create an isolated Python environment for TFC dependencies)
virtualenv==16.7.9 --hash=sha512:f4e7148f1de50fa2e69061e72db211085fc2f44007de4d18ee02a20d34bca30a00d2fe56ff6f3132e696c3f6efd4151863f26dac4c1d43e87b597c47a51c52ad virtualenv==20.0.8 --hash=sha512:8b85fa635c5ec51881aed2238f1e9229d6607644995e26e3f9fe6f8bb6313c51f7b290a6ac1347738866626b1b49d08c5622836dfe2a39ae60f697888bcea615
appdirs==1.4.3 --hash=sha512:b79e9fa76eadee595fe47ea7efd35c4cc72f058a9ed16a95cfa4d91a52c330efba50df7a9926900bbced229cca7bbfb05bbf0a8ee1d46bac2362c98ab9a5154d
distlib==0.3.0 --hash=sha512:6f910a9607569c9023a19aee35be15cf8521ec7c07c5d478e6d555a301d024a2ee1db48562707b238a72c631d75d9dc154d38b39ed51746b66c938ac40671e60
six==1.14.0 --hash=sha512:a6e7e35921ce8f2f8e79a296ea79a9c3515ff6dd7e777d7892fe4988594f1b3a442a68ffb89cf64530b90a32ceeea00e4ab9069bb697629ab4eb7262c68d1b0f
importlib_metadata==1.5.0 --hash=sha512:53e51d4b75c1df19fcb6b32e57fa73ffcb00eede86fee7ac9634f02661360538a74d3546b65a641b68ee84c0d78293fe03d09b65cb85359780822b56f813b926
# importlib_metadata sub-dependencies
filelock==3.0.12 --hash=sha512:d13edd50779bca9842694e0da157ca1fdad9d28166771275049f41dea4b8d8466fc5604b610b6ad64552cdf4c1d3cada9977ca37c6b775c4cc92f333709e8ea3
zipp==3.1.0 --hash=sha512:89170b91cfdc0ef4d85b5316b484c8d6e01985f19bb9f545b11d648e122392efa68d40c66e056b8998fb69af49f4e18707f783be8d500b8957ce3a885662d27c

View File

@ -5,15 +5,16 @@ pyserial==3.4 --hash=sha512:8333ac2843fd136d5d0d63b527b37866f7d18afc3bb33c
# Argon2 (Derives keys that protect persistent user data) # Argon2 (Derives keys that protect persistent user data)
argon2_cffi==19.2.0 --hash=sha512:91c4afc2d0cac14cf4342f198f68afd6477dc5bdf2683476c6f8e253de7b3bdc83b229ce96d0280f656ff33667ab9902c92741b82faee8d8892307cde6199845 argon2_cffi==19.2.0 --hash=sha512:91c4afc2d0cac14cf4342f198f68afd6477dc5bdf2683476c6f8e253de7b3bdc83b229ce96d0280f656ff33667ab9902c92741b82faee8d8892307cde6199845
cffi==1.13.2 --hash=sha512:b8753a0435cc7a2176f8748badc074ec6ffab6698d6be42b1770c85871f85aa7cf60152a8be053c3031b234a286c5cef07267cb812accb704783d74a2675ed3b cffi==1.14.0 --hash=sha512:5b315a65fc8f40622ceef35466546620aaca9dd304f5491a845239659b4066469c5fb3f1683c382eb57f8975caf318e5d88852e3dbb049cde193c9189b88c9c0
pycparser==2.19 --hash=sha512:7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5 pycparser==2.20 --hash=sha512:06dc9cefdcde6b97c96d0452a77db42a629c48ee545edd7ab241763e50e3b3c56d21f9fcce4e206817aa1a597763d948a10ccc73572490d739c89eea7fede0a1
six==1.14.0 --hash=sha512:a6e7e35921ce8f2f8e79a296ea79a9c3515ff6dd7e777d7892fe4988594f1b3a442a68ffb89cf64530b90a32ceeea00e4ab9069bb697629ab4eb7262c68d1b0f six==1.14.0 --hash=sha512:a6e7e35921ce8f2f8e79a296ea79a9c3515ff6dd7e777d7892fe4988594f1b3a442a68ffb89cf64530b90a32ceeea00e4ab9069bb697629ab4eb7262c68d1b0f
# PyNaCl (Handles TCB-side XChaCha20-Poly1305 symmetric encryption) # PyNaCl (Handles TCB-side XChaCha20-Poly1305 symmetric encryption)
PyNaCl==1.3.0 --hash=sha512:c4017c38b026a5c531b15839b8d61d1fae9907ba1960c2f97f4cd67fe0827729346d5186a6d6927ba84f64b4cbfdece12b287aa7750a039f4160831be871cea3 PyNaCl==1.3.0 --hash=sha512:c4017c38b026a5c531b15839b8d61d1fae9907ba1960c2f97f4cd67fe0827729346d5186a6d6927ba84f64b4cbfdece12b287aa7750a039f4160831be871cea3
setuptools==45.1.0 --hash=sha512:761e4c8df239b8d173513b08959b387c1059e3e023ba6b3f6250fade518d6ef29f287ab90dd35d02bb681b410a050b30b2ed44849638b6f98831f4290a4ccd15 setuptools==45.2.0 --hash=sha512:de1ac45cb52e8a28322048e6a2b95015aa6826c49679349a1b579cb46b95cb2ffd62242c861c2fe3e059c0c55d4fdb4384c51b964ca2634b2843263543f8842a
# Duplicate sub-dependencies: cffi, pycparser, six # Duplicate sub-dependencies: cffi, pycparser, six
# Cryptography (Handles TCB-side X448 key exchange) # Cryptography (Handles TCB-side X448 key exchange)
cryptography==2.8 --hash=sha512:184003c89fee74892de25c3e5ec366faea7a5f1fcca3c82b0d5e5f9f797286671a820ca54da5266d6f879ab342c97e25bce9db366c5fb1178690cd5978d4d622 cryptography==2.8 --hash=sha512:184003c89fee74892de25c3e5ec366faea7a5f1fcca3c82b0d5e5f9f797286671a820ca54da5266d6f879ab342c97e25bce9db366c5fb1178690cd5978d4d622 \
--hash=sha512:d8ddabe127ae8d7330d219e284de68b37fa450a27b4cf05334e9115388295b00148d9861c23b1a2e5ea9df0c33a2d27f3e4b25ce9abd3c334f1979920b19c902
# Duplicate sub-dependencies: cffi, pycparser, six # Duplicate sub-dependencies: cffi, pycparser, six

View File

@ -210,7 +210,7 @@ def argon2_kdf(password: str, # Password to derive the key from
[1] https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf [1] https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
[2] https://password-hashing.net/submissions/specs/Catena-v5.pdf [2] https://password-hashing.net/submissions/specs/Catena-v5.pdf
[3] https://crypto.stanford.edu/balloon/ [3] https://crypto.stanford.edu/balloon/
[4] https://tools.ietf.org/html/draft-irtf-cfrg-argon2-06#section-9.4 [4] https://tools.ietf.org/html/draft-irtf-cfrg-argon2-09#section-8.4
[5] https://github.com/P-H-C/phc-winner-argon2 [5] https://github.com/P-H-C/phc-winner-argon2
https://github.com/hynek/argon2_cffi https://github.com/hynek/argon2_cffi
""" """
@ -355,14 +355,14 @@ class X448(object):
fully seeded. This is the same case as with TFC's `csprng()` fully seeded. This is the same case as with TFC's `csprng()`
function. function.
[1] https://github.com/pyca/cryptography/blob/2.7/src/cryptography/hazmat/primitives/asymmetric/x448.py#L38 [1] https://github.com/pyca/cryptography/blob/2.8/src/cryptography/hazmat/primitives/asymmetric/x448.py#L38
[2] https://github.com/pyca/cryptography/blob/2.7/src/cryptography/hazmat/backends/openssl/backend.py#L2445 [2] https://github.com/pyca/cryptography/blob/2.8/src/cryptography/hazmat/backends/openssl/backend.py#L2483
[3] https://github.com/pyca/cryptography/blob/2.7/src/cryptography/hazmat/backends/openssl/backend.py#L115 [3] https://github.com/pyca/cryptography/blob/2.8/src/cryptography/hazmat/backends/openssl/backend.py#L118
[4] https://github.com/pyca/cryptography/blob/2.7/src/cryptography/hazmat/backends/openssl/backend.py#L122 [4] https://github.com/pyca/cryptography/blob/2.8/src/cryptography/hazmat/backends/openssl/backend.py#L125
[5] https://cryptography.io/en/latest/hazmat/backends/openssl/#activate_osrandom_engine [5] https://cryptography.io/en/latest/hazmat/backends/openssl/#activate_osrandom_engine
[6] https://cryptography.io/en/latest/hazmat/backends/openssl/#os-random-engine [6] https://cryptography.io/en/latest/hazmat/backends/openssl/#os-random-engine
[7] https://cryptography.io/en/latest/hazmat/backends/openssl/#os-random-sources [7] https://cryptography.io/en/latest/hazmat/backends/openssl/#os-random-sources
[8] https://github.com/pyca/cryptography/blob/master/src/_cffi_src/openssl/src/osrandom_engine.c#L391 [8] https://github.com/pyca/cryptography/blob/master/src/_cffi_src/openssl/src/osrandom_engine.c#L395
""" """
return X448PrivateKey.generate() return X448PrivateKey.generate()
@ -419,11 +419,13 @@ class X448(object):
return blake2b(shared_secret, digest_size=SYMMETRIC_KEY_LENGTH) return blake2b(shared_secret, digest_size=SYMMETRIC_KEY_LENGTH)
@staticmethod @staticmethod
def derive_keys(dh_shared_key: bytes, def derive_subkeys(dh_shared_key: bytes,
tfc_public_key_user: bytes, tfc_public_key_user: bytes,
tfc_public_key_contact: bytes tfc_public_key_contact: bytes
) -> Tuple[bytes, bytes, bytes, bytes, bytes, bytes]: ) -> Tuple[bytes, bytes, bytes, bytes, bytes, bytes]:
"""Create domain separated message and header keys and fingerprints from shared key. """\
Create domain separated message and header subkeys and fingerprints
from the shared key.
Domain separate unidirectional keys from shared key by using public Domain separate unidirectional keys from shared key by using public
keys as message and the context variable as personalization string. keys as message and the context variable as personalization string.
@ -451,7 +453,7 @@ class X448(object):
key_tuple = tx_mk, rx_mk, tx_hk, rx_hk, tx_fp, rx_fp key_tuple = tx_mk, rx_mk, tx_hk, rx_hk, tx_fp, rx_fp
if len(set(key_tuple)) != len(key_tuple): if len(set(key_tuple)) != len(key_tuple):
raise CriticalError("Derived keys were not unique.") raise CriticalError("Derived subkeys were not unique.")
return key_tuple return key_tuple
@ -515,7 +517,7 @@ def encrypt_and_sign(plaintext: bytes, # Plaintext to encrypt
tested by TFC unit tests. The testing is done in limited scope by tested by TFC unit tests. The testing is done in limited scope by
using the libsodium and official IETF test vectors. using the libsodium and official IETF test vectors.
[1] https://tools.ietf.org/html/draft-irtf-cfrg-xchacha-01 [1] https://tools.ietf.org/html/draft-irtf-cfrg-xchacha-03
[2] https://tools.ietf.org/html/rfc8439 [2] https://tools.ietf.org/html/rfc8439
[3] https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction [3] https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction
[4] https://cr.yp.to/snuffle/keysizes.pdf [4] https://cr.yp.to/snuffle/keysizes.pdf
@ -887,8 +889,8 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
entropy estimator by 1024 bits.[1; pp.59-60] entropy estimator by 1024 bits.[1; pp.59-60]
[1] https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Studies/LinuxRNG/LinuxRNG_EN.pdf [1] https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Studies/LinuxRNG/LinuxRNG_EN.pdf
[2] https://github.com/torvalds/linux/blob/master/drivers/char/random.c#L791 [2] https://github.com/torvalds/linux/blob/master/drivers/char/random.c#L734
[3] https://github.com/torvalds/linux/blob/master/drivers/char/random.c#L1032 [3] https://github.com/torvalds/linux/blob/master/drivers/char/random.c#L952
The ChaCha20 DRNG The ChaCha20 DRNG
================= =================
@ -994,10 +996,10 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
[1] https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Studies/LinuxRNG/LinuxRNG_EN.pdf [1] https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Studies/LinuxRNG/LinuxRNG_EN.pdf
[2] https://lkml.org/lkml/2019/5/30/867 [2] https://lkml.org/lkml/2019/5/30/867
[3] https://github.com/torvalds/linux/blob/master/drivers/char/random.c#L889 [3] https://github.com/torvalds/linux/blob/master/drivers/char/random.c#L810
https://github.com/torvalds/linux/blob/master/drivers/char/random.c#L1058 https://github.com/torvalds/linux/blob/master/drivers/char/random.c#L977
[4] https://github.com/torvalds/linux/blob/master/lib/chacha.c#L87 [4] https://github.com/torvalds/linux/blob/master/lib/crypto/chacha.c#L89
https://github.com/torvalds/linux/blob/master/drivers/char/random.c#L1064 https://github.com/torvalds/linux/blob/master/drivers/char/random.c#L983
GETRANDOM and Python GETRANDOM and Python

View File

@ -64,7 +64,7 @@ class TFCDatabase(object):
os.fsync(f.fileno()) os.fsync(f.fileno())
def verify_file(self, database_name: str) -> bool: def verify_file(self, database_name: str) -> bool:
"""Verify integrity of file content.""" """Verify integrity of database content."""
with open(database_name, 'rb') as f: with open(database_name, 'rb') as f:
purp_data = f.read() purp_data = f.read()

View File

@ -25,8 +25,8 @@ import typing
from typing import Iterable, Iterator, List, Optional, Sized from typing import Iterable, Iterator, List, Optional, Sized
from src.common.database import TFCDatabase from src.common.database import TFCDatabase
from src.common.encoding import bool_to_bytes, pub_key_to_onion_address, str_to_bytes, pub_key_to_short_address from src.common.encoding import (bool_to_bytes, pub_key_to_onion_address, str_to_bytes, pub_key_to_short_address,
from src.common.encoding import bytes_to_bool, onion_address_to_pub_key, bytes_to_str bytes_to_bool, onion_address_to_pub_key, bytes_to_str)
from src.common.exceptions import CriticalError from src.common.exceptions import CriticalError
from src.common.misc import ensure_dir, get_terminal_width, separate_headers, split_byte_string from src.common.misc import ensure_dir, get_terminal_width, separate_headers, split_byte_string
from src.common.output import clear_screen from src.common.output import clear_screen
@ -111,8 +111,8 @@ class Contact(object):
notifications: This setting defines whether, in situations where notifications: This setting defines whether, in situations where
some other window is active, the Receiver Program some other window is active, the Receiver Program
displays a notification about the contact sending a displays a notification about the contact sending
new message to their window. The setting has no a new message to their window. The setting has no
effect on user's Transmitter Program. effect on user's Transmitter Program.
tfc_private_key: This value is an ephemerally stored private key tfc_private_key: This value is an ephemerally stored private key
@ -139,8 +139,8 @@ class Contact(object):
) -> None: ) -> None:
"""Create a new Contact object. """Create a new Contact object.
`self.short_address` is a truncated version of the account used `self.short_address` is the truncated version of the account
to identify TFC account in printed messages. used to identify TFC account in printed messages.
""" """
self.onion_pub_key = onion_pub_key self.onion_pub_key = onion_pub_key
self.nick = nick self.nick = nick
@ -473,8 +473,7 @@ class ContactList(Iterable[Contact], Sized):
KEX_STATUS_UNVERIFIED: f"{ECDHE} (Unverified)", KEX_STATUS_UNVERIFIED: f"{ECDHE} (Unverified)",
KEX_STATUS_VERIFIED: f"{ECDHE} (Verified)", KEX_STATUS_VERIFIED: f"{ECDHE} (Verified)",
KEX_STATUS_NO_RX_PSK: f"{PSK} (No contact key)", KEX_STATUS_NO_RX_PSK: f"{PSK} (No contact key)",
KEX_STATUS_HAS_RX_PSK: PSK KEX_STATUS_HAS_RX_PSK: PSK}
}
# Populate columns with contact data # Populate columns with contact data
for c in self.get_list_of_contacts(): for c in self.get_list_of_contacts():

View File

@ -27,11 +27,11 @@ from typing import Callable, Iterable, Iterator, List, Sized
from src.common.database import TFCDatabase from src.common.database import TFCDatabase
from src.common.db_contacts import Contact from src.common.db_contacts import Contact
from src.common.encoding import bool_to_bytes, int_to_bytes, str_to_bytes, onion_address_to_pub_key, b58encode from src.common.encoding import (bool_to_bytes, int_to_bytes, str_to_bytes, onion_address_to_pub_key,
from src.common.encoding import bytes_to_bool, bytes_to_int, bytes_to_str bytes_to_bool, bytes_to_int, bytes_to_str, b58encode)
from src.common.exceptions import CriticalError from src.common.exceptions import CriticalError
from src.common.misc import ensure_dir, get_terminal_width, round_up, separate_header, separate_headers from src.common.misc import (ensure_dir, get_terminal_width, round_up, separate_header, separate_headers,
from src.common.misc import split_byte_string split_byte_string)
from src.common.statics import (CONTACT_LIST_INDENT, DIR_USER_DATA, DUMMY_GROUP, DUMMY_MEMBER, from src.common.statics import (CONTACT_LIST_INDENT, DIR_USER_DATA, DUMMY_GROUP, DUMMY_MEMBER,
ENCODED_BOOLEAN_LENGTH, ENCODED_INTEGER_LENGTH, GROUP_DB_HEADER_LENGTH, ENCODED_BOOLEAN_LENGTH, ENCODED_INTEGER_LENGTH, GROUP_DB_HEADER_LENGTH,
GROUP_ID_LENGTH, GROUP_STATIC_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH, GROUP_ID_LENGTH, GROUP_STATIC_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH,
@ -266,8 +266,8 @@ class GroupList(Iterable[Group], Sized):
content. The function then removes dummy groups based on header content. The function then removes dummy groups based on header
data. Next, the function updates the group database settings if data. Next, the function updates the group database settings if
necessary. It then splits group data based on header data into necessary. It then splits group data based on header data into
blocks, which are further sliced, and processed if necessary, to blocks, which are further sliced, and processed if necessary,
obtain data required to create Group objects. Finally, if to obtain data required to create Group objects. Finally, if
needed, the function will update the group database content. needed, the function will update the group database content.
""" """
pt_bytes = self.database.load_database() pt_bytes = self.database.load_database()
@ -319,8 +319,8 @@ class GroupList(Iterable[Group], Sized):
members_in_largest_group: int members_in_largest_group: int
) -> bool: ) -> bool:
"""\ """\
Adjust TFC's settings automatically if loaded group database was Adjust TFC's settings automatically if the loaded group database
stored using larger database setting values. was stored using larger database setting values.
If settings had to be adjusted, return True so the method If settings had to be adjusted, return True so the method
`self._load_groups` knows to write changes to a new database. `self._load_groups` knows to write changes to a new database.

View File

@ -27,8 +27,7 @@ from typing import Any, Callable, Dict, List
from src.common.crypto import blake2b, csprng from src.common.crypto import blake2b, csprng
from src.common.database import TFCDatabase from src.common.database import TFCDatabase
from src.common.encoding import int_to_bytes, onion_address_to_pub_key from src.common.encoding import bytes_to_int, int_to_bytes, onion_address_to_pub_key
from src.common.encoding import bytes_to_int
from src.common.exceptions import CriticalError from src.common.exceptions import CriticalError
from src.common.misc import ensure_dir, separate_headers, split_byte_string from src.common.misc import ensure_dir, separate_headers, split_byte_string
from src.common.statics import (DIR_USER_DATA, DUMMY_CONTACT, HARAC_LENGTH, INITIAL_HARAC, KDB_ADD_ENTRY_HEADER, from src.common.statics import (DIR_USER_DATA, DUMMY_CONTACT, HARAC_LENGTH, INITIAL_HARAC, KDB_ADD_ENTRY_HEADER,
@ -52,16 +51,16 @@ class KeySet(object):
Tor Onion Service address. Used to uniquely identify Tor Onion Service address. Used to uniquely identify
the KeySet object. the KeySet object.
tx_mk: Forward secret message key for sent messages. tx_mk: The forward secret message key for sent messages.
rx_mk: Forward secret message key for received messages. rx_mk: The forward secret message key for received messages.
Used only by the Receiver Program. Used only by the Receiver Program.
tx_hk: Static header key used to encrypt and sign the hash tx_hk: The static header key used to encrypt and sign the hash
ratchet counter provided along the encrypted ratchet counter provided along the encrypted
assembly packet. assembly packet.
rx_hk: Static header key used to authenticate and decrypt rx_hk: The static header key used to authenticate and decrypt
the hash ratchet counter of received messages. Used the hash ratchet counter of received messages. Used
only by the Receiver Program. only by the Receiver Program.

View File

@ -60,7 +60,7 @@ def log_writer_loop(queues: Dict[bytes, 'Queue[Any]'], # Dictionary of que
message_log: 'MessageLog', # MessageLog object message_log: 'MessageLog', # MessageLog object
unit_test: bool = False # True, exits loop when UNIT_TEST_QUEUE is no longer empty. unit_test: bool = False # True, exits loop when UNIT_TEST_QUEUE is no longer empty.
) -> None: ) -> None:
"""Write assembly packets to log database. """Write assembly packets to the log database.
When traffic masking is enabled, the fact this loop is run as a When traffic masking is enabled, the fact this loop is run as a
separate process, means the rate at which `sender_loop` outputs separate process, means the rate at which `sender_loop` outputs
@ -84,17 +84,17 @@ def log_writer_loop(queues: Dict[bytes, 'Queue[Any]'], # Dictionary of que
while log_packet_queue.qsize() == 0: while log_packet_queue.qsize() == 0:
time.sleep(0.01) time.sleep(0.01)
traffic_masking, logfile_masking = check_log_setting_queues(traffic_masking, traffic_masking, logfile_masking = check_setting_queues(traffic_masking,
traffic_masking_queue, traffic_masking_queue,
logfile_masking, logfile_masking,
logfile_masking_queue) logfile_masking_queue)
onion_pub_key, assembly_packet, log_messages, log_as_ph, master_key = log_packet_queue.get() onion_pub_key, assembly_packet, log_messages, log_as_ph, master_key = log_packet_queue.get()
# Update log database key # Update the log database key
message_log.database_key = master_key.master_key message_log.database_key = master_key.master_key
# Detect and ignore commands. # Detect commands and ignore them
if onion_pub_key is None: if onion_pub_key is None:
continue continue
@ -135,12 +135,12 @@ def log_writer_loop(queues: Dict[bytes, 'Queue[Any]'], # Dictionary of que
break break
def check_log_setting_queues(traffic_masking: bool, def check_setting_queues(traffic_masking: bool,
traffic_masking_queue: 'Queue[Any]', traffic_masking_queue: 'Queue[Any]',
logfile_masking: bool, logfile_masking: bool,
logfile_masking_queue: 'Queue[Any]' logfile_masking_queue: 'Queue[Any]'
) -> Tuple[bool, bool]: ) -> Tuple[bool, bool]:
"""Check for updates to logging settings.""" """Check queues for updates to traffic masking and logging settings."""
if traffic_masking_queue.qsize(): if traffic_masking_queue.qsize():
traffic_masking = traffic_masking_queue.get() traffic_masking = traffic_masking_queue.get()
@ -159,10 +159,10 @@ def update_logging_state(assembly_packet: bytes,
`logging_state` retains the logging setting for noise packets that `logging_state` retains the logging setting for noise packets that
do not know the log setting of the window. To prevent logging of do not know the log setting of the window. To prevent logging of
noise packets in situation where logging has been disabled, but no noise packets in a situation where logging has been disabled, but no
new message assembly packet carrying the logging setting is received, new message assembly packet carrying the logging setting has been
the LOG_SETTING_QUEUE is checked for up-to-date logging setting for received, the LOG_SETTING_QUEUE is checked for up-to-date logging
every received noise packet. setting for every received noise packet.
""" """
if assembly_packet[:ASSEMBLY_PACKET_HEADER_LENGTH] == P_N_HEADER: if assembly_packet[:ASSEMBLY_PACKET_HEADER_LENGTH] == P_N_HEADER:
if log_setting_queue.qsize(): if log_setting_queue.qsize():
@ -181,7 +181,7 @@ def write_log_entry(assembly_packet: bytes, # Assembly pac
Logging assembly packets allows reconstruction of conversation while Logging assembly packets allows reconstruction of conversation while
protecting metadata about the length of messages alternative log protecting metadata about the length of messages alternative log
file formats could reveal. file formats could reveal to a physical attacker.
Transmitter Program can only log sent messages. This is not useful Transmitter Program can only log sent messages. This is not useful
for recalling conversations but it makes it possible to audit for recalling conversations but it makes it possible to audit
@ -335,7 +335,7 @@ def change_log_db_key(old_key: bytes,
new_key: bytes, new_key: bytes,
settings: 'Settings' settings: 'Settings'
) -> None: ) -> None:
"""Re-encrypt log database with a new master key.""" """Re-encrypt the log database with a new master key."""
ensure_dir(DIR_USER_DATA) ensure_dir(DIR_USER_DATA)
file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs' file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
temp_name = file_name + TEMP_POSTFIX temp_name = file_name + TEMP_POSTFIX
@ -357,7 +357,7 @@ def change_log_db_key(old_key: bytes,
def replace_log_db(settings: 'Settings') -> None: def replace_log_db(settings: 'Settings') -> None:
"""Replace log database with temp file.""" """Replace the log database with the temp file."""
ensure_dir(DIR_USER_DATA) ensure_dir(DIR_USER_DATA)
file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs' file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
temp_name = file_name + TEMP_POSTFIX temp_name = file_name + TEMP_POSTFIX
@ -378,7 +378,7 @@ def remove_logs(contact_list: 'ContactList',
If the selector is a public key, all messages (both the private If the selector is a public key, all messages (both the private
conversation and any associated group messages) sent to and received conversation and any associated group messages) sent to and received
from the associated contact are removed. If the selector is a group from the associated contact are removed. If the selector is a group
ID, only messages for group determined by that group ID are removed. ID, only messages for the group matching that group ID are removed.
""" """
ensure_dir(DIR_USER_DATA) ensure_dir(DIR_USER_DATA)
file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs' file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'

View File

@ -111,14 +111,14 @@ class MasterKey(object):
The generated master key depends on a 256-bit salt and the The generated master key depends on a 256-bit salt and the
password entered by the user. Additional computational strength password entered by the user. Additional computational strength
is added by the slow hash function (Argon2id). The more cores and is added by the slow hash function (Argon2id). The more cores
the faster each core is, and the more memory the system has, the and the faster each core is, and the more memory the system has,
more secure TFC data is under the same password. the more secure TFC data is under the same password.
This method automatically tweaks the Argon2 time and memory cost This method automatically tweaks the Argon2 time and memory cost
parameters according to best practices as determined in parameters according to best practices as determined in
https://tools.ietf.org/html/draft-irtf-cfrg-argon2-04#section-4 https://tools.ietf.org/html/draft-irtf-cfrg-argon2-09#section-4
1) For Argon2 type (y), Argon2id was selected because the 1) For Argon2 type (y), Argon2id was selected because the
adversary might be able to run arbitrary code on Destination adversary might be able to run arbitrary code on Destination
@ -145,93 +145,45 @@ class MasterKey(object):
share the same salt is just 10^(-18).* share the same salt is just 10^(-18).*
* https://en.wikipedia.org/wiki/Birthday_attack * https://en.wikipedia.org/wiki/Birthday_attack
The salt does not need additional protection as the security it The salt does not need additional protection as the security
provides depends on the salt space in relation to the number of it provides depends on the salt space in relation to the
attacked targets (i.e. if two or more physically compromised number of attacked targets (i.e. if two or more physically
systems happen to share the same salt, the attacker can speed up compromised systems happen to share the same salt, the
the attack against those systems with time-memory-trade-off attacker can speed up the attack against those systems with
attack). time-memory-trade-off attack).
6) The tag length isn't utilized. The result of the key derivation is 6) The tag length isn't utilized. The result of the key
the master encryption key itself, which is set to 32 bytes for derivation is the master encryption key itself, which is set
use in XChaCha20-Poly1305. to 32 bytes for use in XChaCha20-Poly1305.
7) Memory wiping feature is not provided. 7) Memory wiping feature is not provided.
To recognize the password is correct, the BLAKE2b hash of the master To recognize the password is correct, the BLAKE2b hash of the
key is stored together with key derivation parameters into the master key is stored together with key derivation parameters
login database. into the login database.
The preimage resistance of BLAKE2b prevents derivation of master The preimage resistance of BLAKE2b prevents derivation of
key from the stored hash, and Argon2id ensures brute force and master key from the stored hash, and Argon2id ensures brute
dictionary attacks against the master password are painfully force and dictionary attacks against the master password are
slow even with GPUs/ASICs/FPGAs, as long as the password is painfully slow even with GPUs/ASICs/FPGAs, as long as the
sufficiently strong. password is sufficiently strong.
""" """
password = MasterKey.new_password() password = MasterKey.new_password()
salt = csprng(ARGON2_SALT_LENGTH) salt = csprng(ARGON2_SALT_LENGTH)
time_cost = ARGON2_MIN_TIME_COST
# Determine the amount of memory used from the amount of free RAM in the system. # Determine the amount of memory used from the amount of free RAM in the system.
memory_cost = self.get_available_memory() memory_cost = self.get_available_memory()
# Determine the amount of threads to use # Determine the number of threads to use
parallelism = multiprocessing.cpu_count() parallelism = multiprocessing.cpu_count()
if self.local_test: if self.local_test:
parallelism = max(ARGON2_MIN_PARALLELISM, parallelism // 2) parallelism = max(ARGON2_MIN_PARALLELISM, parallelism // 2)
# Initial key derivation # Determine time cost
phase("Deriving master key", head=2, offset=0) time_cost, kd_time, master_key = self.determine_time_cost(password, salt, memory_cost, parallelism)
master_key, kd_time = self.timed_key_derivation(password, salt, time_cost, memory_cost, parallelism)
phase("", done=True, tail=1)
# If derivation was too fast, increase time_cost
while kd_time < MIN_KEY_DERIVATION_TIME:
print_on_previous_line()
phase(f"Trying time cost {time_cost+1}")
time_cost += 1
master_key, kd_time = self.timed_key_derivation(password, salt, time_cost, memory_cost, parallelism)
phase(f"{kd_time:.1f}s", done=True)
# At this point time_cost may have value of 1 or it may have increased to e.g. 3, which might make it take
# longer than MAX_KEY_DERIVATION_TIME. If that's the case, it makes no sense to lower it back to 2 because even
# with all memory, time_cost=2 will still be too fast. We therefore accept the time_cost whatever it is.
# If the key derivation time is too long, we do a binary search on the amount
# of memory to use until we hit the desired key derivation time range.
middle = None
# Determine memory cost
if kd_time > MAX_KEY_DERIVATION_TIME: if kd_time > MAX_KEY_DERIVATION_TIME:
memory_cost, master_key = self.determine_memory_cost(password, salt, time_cost, memory_cost, parallelism)
lower_bound = ARGON2_MIN_MEMORY_COST
upper_bound = memory_cost
while kd_time < MIN_KEY_DERIVATION_TIME or kd_time > MAX_KEY_DERIVATION_TIME:
middle = (lower_bound + upper_bound) // 2
print_on_previous_line()
phase(f"Trying memory cost {middle} KiB")
master_key, kd_time = self.timed_key_derivation(password, salt, time_cost, middle, parallelism)
phase(f"{kd_time:.1f}s", done=True)
# The search might fail e.g. if external CPU load causes delay in key derivation, which causes the
# search to continue into wrong branch. In such a situation the search is restarted. The binary search
# is problematic with tight key derivation time target ranges, so if the search keeps restarting,
# increasing MAX_KEY_DERIVATION_TIME (and thus expanding the range) will help finding suitable
# memory_cost value faster. Increasing MAX_KEY_DERIVATION_TIME slightly affects security (positively)
# and user experience (negatively).
if middle == lower_bound or middle == upper_bound:
lower_bound = ARGON2_MIN_MEMORY_COST
upper_bound = self.get_available_memory()
continue
if kd_time < MIN_KEY_DERIVATION_TIME:
lower_bound = middle
elif kd_time > MAX_KEY_DERIVATION_TIME:
upper_bound = middle
memory_cost = middle if middle is not None else memory_cost
# Store values to database # Store values to database
database_data = (salt database_data = (salt
@ -249,12 +201,156 @@ class MasterKey(object):
# the database data. # the database data.
self.database_data = database_data self.database_data = database_data
print_on_previous_line(2) print_on_previous_line()
phase("Deriving master key") phase("Deriving master key")
phase(DONE, delay=1) phase(DONE, delay=1)
return master_key return master_key
def determine_time_cost(self,
password: str,
salt: bytes,
memory_cost: int,
parallelism: int
) -> Tuple[int, float, bytes]:
"""Find suitable time_cost value for Argon2id.
There are two acceptable time_cost values.
1. A time_cost value that together with all available memory
sets the key derivation time between MIN_KEY_DERIVATION_TIME
and MAX_KEY_DERIVATION_TIME. If during the search we find
such suitable time_cost value, we accept it as such.
2. In a situation where no time_cost value is suitable alone,
there will exist some time_cost value `t` that makes key
derivation too fast, and another time_cost value `t+1` that
makes key derivation too slow. In this case we are interested
in the latter value, as unlike `t`, the value `t+1` can be
fine-tuned to suitable key derivation time range by adjusting
the memory_cost parameter.
As time_cost has no upper limit, and as the amount of available
memory has tremendous effect on how long one round takes, it's
difficult to determine the upper bound for a time_cost binary
search. We therefore start with a single round, and by
benchmarking it, estimate how many rounds are needed to reach
the target zone. After every try, we update our time_cost
candidate based on new average time per round estimate, a value
that gets more accurate as the search progresses. If this
method isn't able to suggest a value larger than 1, we increase
time_cost by 1 anyway to prevent an Alderson loop.
Every time the time_cost value is increased, we update the lower
bound to narrow the search space of the binary search we can
switch to immediately, once the MAX_KEY_DERIVATION_TIME is
exceeded (i.e. once an upper bound is found). At that point, the
time_cost `t+1` can be found in log(n) time.
"""
lower_bound = ARGON2_MIN_TIME_COST # type: int
upper_bound = None # type: Optional[int]
time_cost = lower_bound
print(2*'\n')
while True:
print_on_previous_line()
phase(f"Trying time cost {time_cost}")
master_key, kd_time = self.timed_key_derivation(password, salt, time_cost, memory_cost, parallelism)
phase(f"{kd_time:.1f}s", done=True)
# Sentinel that checks if the binary search has ended, and that restarts
# the search if kd_time repeats. This prevents an Alderson loop.
if upper_bound is not None and time_cost in [lower_bound, upper_bound]: # pragma: no cover
lower_bound = ARGON2_MIN_TIME_COST
upper_bound = None
continue
if MIN_KEY_DERIVATION_TIME <= kd_time <= MAX_KEY_DERIVATION_TIME:
break
if kd_time < MIN_KEY_DERIVATION_TIME:
lower_bound = time_cost
if upper_bound is None:
avg_time_per_round = kd_time / time_cost
time_cost_candidate = math.floor(MAX_KEY_DERIVATION_TIME / avg_time_per_round)
time_cost = max(time_cost+1, time_cost_candidate)
else:
if time_cost + 1 == upper_bound:
time_cost += 1
break
time_cost = math.floor((lower_bound + upper_bound) / 2)
elif kd_time > MAX_KEY_DERIVATION_TIME:
upper_bound = time_cost
# Sentinel: If even a single round takes too long, it's the `t+1` we're looking for.
if time_cost == 1:
break
# Sentinel: If the current time_cost value (that was too large) is one
# greater than the lower_bound, we know current time_cost is at `t+1`.
if time_cost == lower_bound + 1:
break
# Otherwise we know the current time_cost is at least two integers greater
# than `t`. Our best candidate for `t` is lower_bound, but for all we know,
# `t` might be a much greater value. So we continue binary search for `t+1`
time_cost = math.floor((lower_bound + upper_bound) / 2)
return time_cost, kd_time, master_key
def determine_memory_cost(self,
password: str,
salt: bytes,
time_cost: int,
memory_cost: int,
parallelism: int,
) -> Tuple[int, bytes]:
"""Determine suitable memory_cost value for Argon2id.
If we reached this function, it means we found a `t+1` value for
time_cost (explained in the `determine_time_cost` function). We
therefore do a binary search on the amount of memory to use
until we hit the desired key derivation time range.
"""
lower_bound = ARGON2_MIN_MEMORY_COST
upper_bound = memory_cost
while True:
memory_cost = int(round((lower_bound + upper_bound) // 2, -3))
print_on_previous_line()
phase(f"Trying memory cost {memory_cost} KiB")
master_key, kd_time = self.timed_key_derivation(password, salt, time_cost, memory_cost, parallelism)
phase(f"{kd_time:.1f}s", done=True)
# If we found a suitable memory_cost value, we accept the key and the memory_cost.
if MIN_KEY_DERIVATION_TIME <= kd_time <= MAX_KEY_DERIVATION_TIME:
return memory_cost, master_key
# The search might fail e.g. if external CPU load causes delay in key
# derivation, which causes the search to continue into wrong branch. In
# such a situation the search is restarted. The binary search is problematic
# with tight key derivation time target ranges, so if the search keeps
# restarting, increasing MAX_KEY_DERIVATION_TIME (and thus expanding the
# range) will help finding suitable memory_cost value faster. Increasing
# MAX_KEY_DERIVATION_TIME slightly affects security (positively) and user
# experience (negatively).
if memory_cost == lower_bound or memory_cost == upper_bound:
lower_bound = ARGON2_MIN_MEMORY_COST
upper_bound = self.get_available_memory()
continue
if kd_time < MIN_KEY_DERIVATION_TIME:
lower_bound = memory_cost
elif kd_time > MAX_KEY_DERIVATION_TIME:
upper_bound = memory_cost
def replace_database_data(self) -> None: def replace_database_data(self) -> None:
"""Store cached database data into database.""" """Store cached database data into database."""
if self.database_data is not None: if self.database_data is not None:

View File

@ -26,8 +26,8 @@ import typing
from typing import Union from typing import Union
from src.common.database import TFCDatabase from src.common.database import TFCDatabase
from src.common.encoding import bool_to_bytes, double_to_bytes, int_to_bytes from src.common.encoding import (bool_to_bytes, double_to_bytes, int_to_bytes,
from src.common.encoding import bytes_to_bool, bytes_to_double, bytes_to_int bytes_to_bool, bytes_to_double, bytes_to_int)
from src.common.exceptions import CriticalError, SoftError from src.common.exceptions import CriticalError, SoftError
from src.common.input import yes from src.common.input import yes
from src.common.misc import ensure_dir, get_terminal_width, round_up from src.common.misc import ensure_dir, get_terminal_width, round_up
@ -53,6 +53,7 @@ class Settings(object):
master_key: 'MasterKey', # MasterKey object master_key: 'MasterKey', # MasterKey object
operation: str, # Operation mode of the program (Tx or Rx) operation: str, # Operation mode of the program (Tx or Rx)
local_test: bool, # Local testing setting from command-line argument local_test: bool, # Local testing setting from command-line argument
qubes: bool = False # Qubes setting from command-line argument
) -> None: ) -> None:
"""Create a new Settings object. """Create a new Settings object.
@ -91,6 +92,7 @@ class Settings(object):
self.master_key = master_key self.master_key = master_key
self.software_operation = operation self.software_operation = operation
self.local_testing_mode = local_test self.local_testing_mode = local_test
self.qubes = qubes
self.file_name = f'{DIR_USER_DATA}{operation}_settings' self.file_name = f'{DIR_USER_DATA}{operation}_settings'
self.database = TFCDatabase(self.file_name, master_key) self.database = TFCDatabase(self.file_name, master_key)
@ -199,7 +201,7 @@ class Settings(object):
Settings.validate_max_number_of_groups(key, value, group_list) Settings.validate_max_number_of_groups(key, value, group_list)
Settings.validate_max_number_of_contacts(key, value, contact_list) Settings.validate_max_number_of_contacts(key, value, contact_list)
Settings.validate_new_message_notify_duration(key, value) Settings.validate_new_message_notify_duration(key, value)
Settings.validate_traffic_maskig_delay(key, value, contact_list) Settings.validate_traffic_masking_delay(key, value, contact_list)
@staticmethod @staticmethod
def validate_database_limit(key: str, value: 'SettingType') -> None: def validate_database_limit(key: str, value: 'SettingType') -> None:
@ -248,10 +250,10 @@ class Settings(object):
raise SoftError("Error: Too small value for message notify duration.", head_clear=True) raise SoftError("Error: Too small value for message notify duration.", head_clear=True)
@staticmethod @staticmethod
def validate_traffic_maskig_delay(key: str, def validate_traffic_masking_delay(key: str,
value: 'SettingType', value: 'SettingType',
contact_list: 'ContactList' contact_list: 'ContactList'
) -> None: ) -> None:
"""Validate setting value for traffic masking delays.""" """Validate setting value for traffic masking delays."""
if key in ["tm_static_delay", "tm_random_delay"]: if key in ["tm_static_delay", "tm_random_delay"]:

View File

@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License
along with TFC. If not, see <https://www.gnu.org/licenses/>. along with TFC. If not, see <https://www.gnu.org/licenses/>.
""" """
import base64
import hashlib import hashlib
import json import json
import multiprocessing.connection import multiprocessing.connection
@ -36,15 +37,17 @@ from typing import Any, Dict, Optional, Tuple, Union
from serial.serialutil import SerialException from serial.serialutil import SerialException
from src.common.exceptions import CriticalError, graceful_exit, SoftError from src.common.exceptions import CriticalError, graceful_exit, SoftError
from src.common.input import yes from src.common.input import box_input, yes
from src.common.misc import calculate_race_condition_delay, ensure_dir, ignored, get_terminal_width from src.common.misc import (calculate_race_condition_delay, ensure_dir, ignored, get_terminal_width,
from src.common.misc import separate_trailer separate_trailer, split_byte_string, validate_ip_address)
from src.common.output import m_print, phase, print_on_previous_line from src.common.output import m_print, phase, print_on_previous_line
from src.common.reed_solomon import ReedSolomonError, RSCodec from src.common.reed_solomon import ReedSolomonError, RSCodec
from src.common.statics import (BAUDS_PER_BYTE, DIR_USER_DATA, DONE, DST_DD_LISTEN_SOCKET, DST_LISTEN_SOCKET, from src.common.statics import (BAUDS_PER_BYTE, DIR_USER_DATA, DONE, DST_DD_LISTEN_SOCKET, DST_LISTEN_SOCKET,
GATEWAY_QUEUE, LOCALHOST, LOCAL_TESTING_PACKET_DELAY, MAX_INT, NC, GATEWAY_QUEUE, LOCALHOST, LOCAL_TESTING_PACKET_DELAY, MAX_INT, NC,
QUBES_DST_LISTEN_SOCKET, QUBES_RX_IP_ADDR_FILE, QUBES_SRC_LISTEN_SOCKET,
PACKET_CHECKSUM_LENGTH, RECEIVER, RELAY, RP_LISTEN_SOCKET, RX, PACKET_CHECKSUM_LENGTH, RECEIVER, RELAY, RP_LISTEN_SOCKET, RX,
SERIAL_RX_MIN_TIMEOUT, SETTINGS_INDENT, SRC_DD_LISTEN_SOCKET, TRANSMITTER, TX) SERIAL_RX_MIN_TIMEOUT, SETTINGS_INDENT, SOCKET_BUFFER_SIZE, SRC_DD_LISTEN_SOCKET,
TRANSMITTER, TX, US_BYTE)
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from multiprocessing import Queue from multiprocessing import Queue
@ -59,9 +62,10 @@ def gateway_loop(queues: Dict[bytes, 'Queue[Tuple[datetime, bytes]]'],
Also place the current timestamp to queue to be delivered to the Also place the current timestamp to queue to be delivered to the
Receiver Program. The timestamp is used both to notify when the sent Receiver Program. The timestamp is used both to notify when the sent
message was received by Relay Program, and as part of a commitment message was received by the Relay Program, and as part of a
scheme: For more information, see the section on "Covert channel commitment scheme: For more information, see the section on "Covert
based on user interaction" under TFC's Security Design wiki article. channel based on user interaction" under TFC's Security Design wiki
article.
""" """
queue = queues[GATEWAY_QUEUE] queue = queues[GATEWAY_QUEUE]
@ -75,20 +79,23 @@ def gateway_loop(queues: Dict[bytes, 'Queue[Tuple[datetime, bytes]]'],
class Gateway(object): class Gateway(object):
"""\ """\
Gateway object is a wrapper for interfaces that connect Gateway object is a wrapper for interfaces that connect
Source/Destination Computer with the Networked computer. Source/Destination Computer with the Networked Computer.
""" """
def __init__(self, def __init__(self,
operation: str, operation: str,
local_test: bool, local_test: bool,
dd_sockets: bool dd_sockets: bool,
qubes: bool,
) -> None: ) -> None:
"""Create a new Gateway object.""" """Create a new Gateway object."""
self.settings = GatewaySettings(operation, local_test, dd_sockets) self.settings = GatewaySettings(operation, local_test, dd_sockets, qubes)
self.tx_serial = None # type: Optional[serial.Serial] self.tx_serial = None # type: Optional[serial.Serial]
self.rx_serial = None # type: Optional[serial.Serial] self.rx_serial = None # type: Optional[serial.Serial]
self.rx_socket = None # type: Optional[multiprocessing.connection.Connection] self.rx_socket = None # type: Optional[multiprocessing.connection.Connection]
self.tx_socket = None # type: Optional[multiprocessing.connection.Connection] self.tx_socket = None # type: Optional[multiprocessing.connection.Connection]
self.txq_socket = None # type: Optional[socket.socket]
self.rxq_socket = None # type: Optional[socket.socket]
# Initialize Reed-Solomon erasure code handler # Initialize Reed-Solomon erasure code handler
self.rs = RSCodec(2 * self.settings.session_serial_error_correction) self.rs = RSCodec(2 * self.settings.session_serial_error_correction)
@ -102,6 +109,11 @@ class Gateway(object):
self.client_establish_socket() self.client_establish_socket()
if self.settings.software_operation in [NC, RX]: if self.settings.software_operation in [NC, RX]:
self.server_establish_socket() self.server_establish_socket()
elif qubes:
if self.settings.software_operation in [TX, NC]:
self.qubes_client_establish_socket()
if self.settings.software_operation in [NC, RX]:
self.qubes_server_establish_socket()
else: else:
self.establish_serial() self.establish_serial()
@ -141,6 +153,19 @@ class Gateway(object):
except SerialException: except SerialException:
raise CriticalError("SerialException. Ensure $USER is in the dialout group by restarting this computer.") raise CriticalError("SerialException. Ensure $USER is in the dialout group by restarting this computer.")
def write_udp_packet(self, packet: bytes) -> None:
"""Split packet to smaller parts and transmit them over the socket."""
udp_port = QUBES_SRC_LISTEN_SOCKET if self.settings.software_operation == TX else QUBES_DST_LISTEN_SOCKET
packet = base64.b85encode(packet)
packets = split_byte_string(packet, SOCKET_BUFFER_SIZE)
if self.txq_socket is not None:
for p in packets:
self.txq_socket.sendto(p, (self.settings.rx_udp_ip, udp_port))
time.sleep(0.000001)
self.txq_socket.sendto(US_BYTE, (self.settings.rx_udp_ip, udp_port))
def write(self, orig_packet: bytes) -> None: def write(self, orig_packet: bytes) -> None:
"""Add error correction data and output data via socket/serial interface. """Add error correction data and output data via socket/serial interface.
@ -157,6 +182,10 @@ class Gateway(object):
time.sleep(LOCAL_TESTING_PACKET_DELAY) time.sleep(LOCAL_TESTING_PACKET_DELAY)
except BrokenPipeError: except BrokenPipeError:
raise CriticalError("Relay IPC server disconnected.", exit_code=0) raise CriticalError("Relay IPC server disconnected.", exit_code=0)
elif self.txq_socket is not None:
self.write_udp_packet(packet)
elif self.tx_serial is not None: elif self.tx_serial is not None:
try: try:
self.tx_serial.write(packet) self.tx_serial.write(packet)
@ -180,6 +209,24 @@ class Gateway(object):
except EOFError: except EOFError:
raise CriticalError("Relay IPC client disconnected.", exit_code=0) raise CriticalError("Relay IPC client disconnected.", exit_code=0)
def read_qubes_socket(self) -> bytes:
"""Read packet from Qubes' socket interface."""
if self.rxq_socket is None:
raise CriticalError("Socket interface has not been initialized.")
while True:
try:
read_buffer = bytearray()
while True:
read = self.rxq_socket.recv(SOCKET_BUFFER_SIZE)
if read == US_BYTE:
return read_buffer
read_buffer.extend(read)
except (EOFError, KeyboardInterrupt):
pass
def read_serial(self) -> bytes: def read_serial(self) -> bytes:
"""Read packet from serial interface. """Read packet from serial interface.
@ -215,8 +262,11 @@ class Gateway(object):
def read(self) -> bytes: def read(self) -> bytes:
"""Read data via socket/serial interface.""" """Read data via socket/serial interface."""
data = (self.read_socket() if self.settings.local_testing_mode else self.read_serial()) if self.settings.local_testing_mode:
return data return self.read_socket()
if self.settings.qubes:
return self.read_qubes_socket()
return self.read_serial()
def add_error_correction(self, packet: bytes) -> bytes: def add_error_correction(self, packet: bytes) -> bytes:
"""Add error correction to packet that will be output. """Add error correction to packet that will be output.
@ -230,8 +280,10 @@ class Gateway(object):
If error correction is set to 0, errors are only detected. This If error correction is set to 0, errors are only detected. This
is done by using a BLAKE2b based, 128-bit checksum. is done by using a BLAKE2b based, 128-bit checksum.
If Qubes is used, Reed-Solomon is not used as it only slows down data transfer.
""" """
if self.settings.session_serial_error_correction: if self.settings.session_serial_error_correction and not self.settings.qubes:
packet = self.rs.encode(packet) packet = self.rs.encode(packet)
else: else:
packet = packet + hashlib.blake2b(packet, digest_size=PACKET_CHECKSUM_LENGTH).digest() packet = packet + hashlib.blake2b(packet, digest_size=PACKET_CHECKSUM_LENGTH).digest()
@ -239,7 +291,13 @@ class Gateway(object):
def detect_errors(self, packet: bytes) -> bytes: def detect_errors(self, packet: bytes) -> bytes:
"""Handle received packet error detection and/or correction.""" """Handle received packet error detection and/or correction."""
if self.settings.session_serial_error_correction: if self.settings.qubes:
try:
packet = base64.b85decode(packet)
except ValueError:
raise SoftError("Error: Received packet had invalid Base85 encoding.")
if self.settings.session_serial_error_correction and not self.settings.qubes:
try: try:
packet, _ = self.rs.decode(packet) packet, _ = self.rs.decode(packet)
return bytes(packet) return bytes(packet)
@ -280,6 +338,30 @@ class Gateway(object):
return f'/dev/{self.settings.built_in_serial_interface}' return f'/dev/{self.settings.built_in_serial_interface}'
raise CriticalError(f"Error: /dev/{self.settings.built_in_serial_interface} was not found.") raise CriticalError(f"Error: /dev/{self.settings.built_in_serial_interface} was not found.")
# Qubes
def qubes_client_establish_socket(self) -> None:
"""Establish Qubes socket for outgoing data."""
self.txq_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def qubes_server_establish_socket(self) -> None:
"""Establish Qubes socket for incoming data."""
udp_port = QUBES_SRC_LISTEN_SOCKET if self.settings.software_operation == NC else QUBES_DST_LISTEN_SOCKET
self.rxq_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.rxq_socket.bind((self.get_local_ip_addr(), udp_port))
@staticmethod
def get_local_ip_addr() -> str:
"""Get local IP address of the system."""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('192.0.0.8', 1027))
except socket.error:
raise CriticalError("Socket error")
ip_address = s.getsockname()[0] # type: str
return ip_address
# Local testing # Local testing
def server_establish_socket(self) -> None: def server_establish_socket(self) -> None:
@ -354,19 +436,20 @@ class GatewaySettings(object):
unencrypted JSON database. unencrypted JSON database.
The reason these settings are in plaintext is it protects the system The reason these settings are in plaintext is it protects the system
from inconsistent state of serial settings: Would the user reconfigure from an inconsistent serial setting state: Would the user change one
their serial settings, and would the setting altering packet to or more settings of their serial interfaces, and would the setting
Receiver Program drop, Relay Program could in some situations no adjusting packet to Receiver Program drop, Relay Program could in
longer communicate with the Receiver Program. some situations no longer communicate with the Receiver Program.
Serial interface settings are not sensitive enough to justify the Serial interface settings are not sensitive enough to justify the
inconvenience of encrypting the setting values. inconveniences that would result from encrypting the setting values.
""" """
def __init__(self, def __init__(self,
operation: str, operation: str,
local_test: bool, local_test: bool,
dd_sockets: bool dd_sockets: bool,
qubes: bool
) -> None: ) -> None:
"""Create a new Settings object. """Create a new Settings object.
@ -379,11 +462,13 @@ class GatewaySettings(object):
self.serial_error_correction = 5 self.serial_error_correction = 5
self.use_serial_usb_adapter = True self.use_serial_usb_adapter = True
self.built_in_serial_interface = 'ttyS0' self.built_in_serial_interface = 'ttyS0'
self.rx_udp_ip = ''
self.software_operation = operation self.software_operation = operation
self.local_testing_mode = local_test self.local_testing_mode = local_test
self.data_diode_sockets = dd_sockets self.data_diode_sockets = dd_sockets
self.qubes = qubes
self.all_keys = list(vars(self).keys()) self.all_keys = list(vars(self).keys())
self.key_list = self.all_keys[:self.all_keys.index('software_operation')] self.key_list = self.all_keys[:self.all_keys.index('software_operation')]
self.defaults = {k: self.__dict__[k] for k in self.key_list} self.defaults = {k: self.__dict__[k] for k in self.key_list}
@ -428,7 +513,7 @@ class GatewaySettings(object):
Ensure that the serial interface is available before proceeding. Ensure that the serial interface is available before proceeding.
""" """
if not self.local_testing_mode: if not self.local_testing_mode and not self.qubes:
name = {TX: TRANSMITTER, NC: RELAY, RX: RECEIVER}[self.software_operation] name = {TX: TRANSMITTER, NC: RELAY, RX: RECEIVER}[self.software_operation]
self.use_serial_usb_adapter = yes(f"Use USB-to-serial/TTL adapter for {name} Computer?", head=1, tail=1) self.use_serial_usb_adapter = yes(f"Use USB-to-serial/TTL adapter for {name} Computer?", head=1, tail=1)
@ -444,6 +529,22 @@ class GatewaySettings(object):
m_print(f"Error: Serial interface /dev/{self.built_in_serial_interface} not found.") m_print(f"Error: Serial interface /dev/{self.built_in_serial_interface} not found.")
self.setup() self.setup()
if self.qubes and self.software_operation != RX:
# Check if IP address was stored by the installer.
if os.path.isfile(QUBES_RX_IP_ADDR_FILE):
cached_ip = open(QUBES_RX_IP_ADDR_FILE).read().strip()
os.remove(QUBES_RX_IP_ADDR_FILE)
if validate_ip_address(cached_ip) == '':
self.rx_udp_ip = cached_ip
return
# If we reach this point, no cached IP was found, prompt for IP address from the user.
rx_device, short = ('Networked', 'NET') if self.software_operation == TX else ('Destination', 'DST')
m_print(f"Enter the IP address of the {rx_device} Computer", head=1, tail=1)
self.rx_udp_ip = box_input(f"{short} IP-address", expected_len=15, validator=validate_ip_address, tail=1)
def store_settings(self) -> None: def store_settings(self) -> None:
"""Store serial settings in JSON format.""" """Store serial settings in JSON format."""
serialized = json.dumps(self, default=(lambda o: {k: self.__dict__[k] for k in self.key_list}), indent=4) serialized = json.dumps(self, default=(lambda o: {k: self.__dict__[k] for k in self.key_list}), indent=4)
@ -484,36 +585,75 @@ class GatewaySettings(object):
def check_missing_settings(self, json_dict: Any) -> None: def check_missing_settings(self, json_dict: Any) -> None:
"""Check for missing JSON fields and invalid values.""" """Check for missing JSON fields and invalid values."""
for key in self.key_list: for key in self.key_list:
if key not in json_dict: try:
m_print([f"Error: Missing setting '{key}' in '{self.file_name}'.", self.check_key_in_key_list(key, json_dict)
f"The value has been set to default ({self.defaults[key]})."], head=1, tail=1)
setattr(self, key, self.defaults[key]) if key == 'serial_baudrate':
self.validate_serial_baudrate(key, json_dict)
elif key == 'serial_error_correction':
self.validate_serial_error_correction(key, json_dict)
elif key == 'use_serial_usb_adapter':
self.validate_serial_usb_adapter_value(key, json_dict)
elif key == 'built_in_serial_interface':
self.validate_serial_interface_value(key, json_dict)
elif key == 'rx_udp_ip':
json_dict[key] = self.validate_rx_udp_ip_address(key, json_dict)
except SoftError:
continue continue
# Closer inspection of each setting value
if key == 'serial_baudrate' and json_dict[key] not in serial.Serial().BAUDRATES:
self.invalid_setting(key, json_dict)
continue
elif key == 'serial_error_correction' and (not isinstance(json_dict[key], int) or json_dict[key] < 0):
self.invalid_setting(key, json_dict)
continue
elif key == 'use_serial_usb_adapter':
if not isinstance(json_dict[key], bool):
self.invalid_setting(key, json_dict)
continue
elif key == 'built_in_serial_interface':
if not isinstance(json_dict[key], str):
self.invalid_setting(key, json_dict)
continue
if not any(json_dict[key] == f for f in os.listdir('/sys/class/tty')):
self.invalid_setting(key, json_dict)
continue
setattr(self, key, json_dict[key]) setattr(self, key, json_dict[key])
def check_key_in_key_list(self, key: str, json_dict: Any) -> None:
"""Check if the setting's key value is in the setting dictionary."""
if key not in json_dict:
m_print([f"Error: Missing setting '{key}' in '{self.file_name}'.",
f"The value has been set to default ({self.defaults[key]})."], head=1, tail=1)
setattr(self, key, self.defaults[key])
raise SoftError("Missing key", output=False)
def validate_serial_usb_adapter_value(self, key: str, json_dict: Any) -> None:
"""Validate the serial usb adapter setting value."""
if not isinstance(json_dict[key], bool):
self.invalid_setting(key, json_dict)
raise SoftError("Invalid value", output=False)
def validate_serial_baudrate(self, key: str, json_dict: Any) -> None:
"""Validate the serial baudrate setting value."""
if json_dict[key] not in serial.Serial().BAUDRATES:
self.invalid_setting(key, json_dict)
raise SoftError("Invalid value", output=False)
def validate_serial_error_correction(self, key: str, json_dict: Any) -> None:
"""Validate the serial error correction setting value."""
if not isinstance(json_dict[key], int) or json_dict[key] < 0:
self.invalid_setting(key, json_dict)
raise SoftError("Invalid value", output=False)
def validate_serial_interface_value(self, key: str, json_dict: Any) -> None:
"""Validate the serial interface setting value."""
if not isinstance(json_dict[key], str):
self.invalid_setting(key, json_dict)
raise SoftError("Invalid value", output=False)
if not any(json_dict[key] == f for f in os.listdir('/sys/class/tty')):
self.invalid_setting(key, json_dict)
raise SoftError("Invalid value", output=False)
def validate_rx_udp_ip_address(self, key: str, json_dict: Any) -> str:
"""Validate IP address of receiving Qubes VM."""
if self.qubes:
if not isinstance(json_dict[key], str) or validate_ip_address(json_dict[key]) != '':
self.setup()
return self.rx_udp_ip
rx_udp_ip = json_dict[key] # type: str
return rx_udp_ip
def change_setting(self, key: str, value_str: str) -> None: def change_setting(self, key: str, value_str: str) -> None:
"""Parse, update and store new setting value.""" """Parse, update and store new setting value."""
attribute = self.__getattribute__(key) attribute = self.__getattribute__(key)

View File

@ -149,7 +149,7 @@ def get_b58_key(key_type: str, # The type of Base58 key to be enter
raise CriticalError("Invalid key type") raise CriticalError("Invalid key type")
while True: while True:
rx_pk = box_input(box_msg, key_type=key_type, guide=not settings.local_testing_mode) rx_pk = box_input(box_msg, key_type=key_type, guide=not (settings.local_testing_mode or settings.qubes))
rx_pk = ''.join(rx_pk.split()) rx_pk = ''.join(rx_pk.split())
if key_type == B58_PUBLIC_KEY and rx_pk == '': if key_type == B58_PUBLIC_KEY and rx_pk == '':
@ -175,8 +175,8 @@ def nc_bypass_msg(key: str, settings: 'Settings') -> None:
key. Without the ciphertext, e.g. a visually collected local key key. Without the ciphertext, e.g. a visually collected local key
decryption key is useless. decryption key is useless.
""" """
m = {NC_BYPASS_START: "Bypass Networked Computer if needed. Press <Enter> to send local key.", m = {NC_BYPASS_START: "Bypass the Networked Computer if needed. Press <Enter> to send local key.",
NC_BYPASS_STOP: "Remove bypass of Networked Computer. Press <Enter> to continue."} NC_BYPASS_STOP: "Remove bypass of the Networked Computer. Press <Enter> to continue."}
if settings.nc_bypass_messages: if settings.nc_bypass_messages:
m_print(m[key], manual_proceed=True, box=True, head=(1 if key == NC_BYPASS_STOP else 0)) m_print(m[key], manual_proceed=True, box=True, head=(1 if key == NC_BYPASS_STOP else 0))

View File

@ -27,6 +27,7 @@ import math
import os import os
import random import random
import shutil import shutil
import socket
import subprocess import subprocess
import sys import sys
import time import time
@ -60,13 +61,13 @@ def calculate_race_condition_delay(serial_error_correction: int,
Calculate the delay required to prevent Relay Program race condition. Calculate the delay required to prevent Relay Program race condition.
When Transmitter Program outputs a command to exit or wipe data, When Transmitter Program outputs a command to exit or wipe data,
Relay program will also receive a copy of the command. If Relay Relay program will also receive a copy of the command. If the Relay
Program acts on the command too early, Receiver Program will not Program acts on the command too early, the Receiver Program will not
receive the exit/wipe command at all. receive the exit/wipe command at all.
This program calculates the delay Transmitter Program should wait This function calculates the delay Transmitter Program should wait
before outputting command for Relay Program, to ensure Receiver before outputting command to the Relay Program, to ensure the
Program has received the encrypted command. Receiver Program has received its encrypted command.
""" """
rs = RSCodec(2 * serial_error_correction) rs = RSCodec(2 * serial_error_correction)
message_length = PACKET_LENGTH + ONION_ADDRESS_LENGTH message_length = PACKET_LENGTH + ONION_ADDRESS_LENGTH
@ -276,7 +277,7 @@ def power_off_system() -> None:
os.system(POWEROFF) os.system(POWEROFF)
def process_arguments() -> Tuple[str, bool, bool]: def process_arguments() -> Tuple[str, bool, bool, bool]:
"""Load program-specific settings from command line arguments. """Load program-specific settings from command line arguments.
The arguments are determined by the desktop entries and in the The arguments are determined by the desktop entries and in the
@ -305,10 +306,16 @@ def process_arguments() -> Tuple[str, bool, bool]:
dest='data_diode_sockets', dest='data_diode_sockets',
help="use data diode simulator sockets during local testing mode") help="use data diode simulator sockets during local testing mode")
parser.add_argument('-q',
action='store_true',
default=False,
dest='qubes',
help="output data as UDP packets. Allows running TFC in qubes")
args = parser.parse_args() args = parser.parse_args()
operation = RX if args.operation else TX operation = RX if args.operation else TX
return operation, args.local_test, args.data_diode_sockets return operation, args.local_test, args.data_diode_sockets, args.qubes
def readable_size(size: int) -> str: def readable_size(size: int) -> str:
@ -477,6 +484,15 @@ def validate_group_name(group_name: str, # Name of the group
return error_msg return error_msg
def validate_ip_address(ip_address: str, *_: Any) -> str:
"""Validate the IP address."""
try:
socket.inet_aton(ip_address)
return ''
except socket.error:
return 'Invalid IP address'
def validate_key_exchange(key_ex: str, # Key exchange selection to validate def validate_key_exchange(key_ex: str, # Key exchange selection to validate
*_: Any # Unused arguments *_: Any # Unused arguments
) -> str: # Error message if validation failed, else empty string ) -> str: # Error message if validation failed, else empty string

View File

@ -205,9 +205,9 @@ def print_key(message: str, # Instructive messag
) -> None: ) -> None:
"""Print a symmetric key in WIF format. """Print a symmetric key in WIF format.
If local testing is not enabled, this function adds spacing in the If serial-interface based platform is used, this function adds
middle of the key, as well as guide letters to help the user keep spacing in the middle of the key, as well as guide letters to help
track of typing progress: the user keep track of typing progress:
Local key encryption keys: Local key encryption keys:
@ -220,7 +220,7 @@ def print_key(message: str, # Instructive messag
4EcuqaD ddsdsuc gBX2PY2 qR8hReA aeSN2oh JB9w5Cv q6BQjDa PPgzSvW 932aHio sT42SKJ Gu2PpS1 Za3Xrao 4EcuqaD ddsdsuc gBX2PY2 qR8hReA aeSN2oh JB9w5Cv q6BQjDa PPgzSvW 932aHio sT42SKJ Gu2PpS1 Za3Xrao
""" """
b58key = b58encode(key_bytes, public_key) b58key = b58encode(key_bytes, public_key)
if settings.local_testing_mode: if settings.local_testing_mode or settings.qubes:
m_print([message, b58key], box=True) m_print([message, b58key], box=True)
else: else:
guide, chunk_length = (B58_PUBLIC_KEY_GUIDE, 7) if public_key else (B58_LOCAL_KEY_GUIDE, 3) guide, chunk_length = (B58_PUBLIC_KEY_GUIDE, 7) if public_key else (B58_LOCAL_KEY_GUIDE, 3)

View File

@ -21,7 +21,7 @@ along with TFC. If not, see <https://www.gnu.org/licenses/>.
"""Program details""" """Program details"""
TFC = 'TFC' TFC = 'TFC'
VERSION = '1.20.02' VERSION = '1.20.03'
TRANSMITTER = 'Transmitter' TRANSMITTER = 'Transmitter'
RECEIVER = 'Receiver' RECEIVER = 'Receiver'
RELAY = 'Relay' RELAY = 'Relay'
@ -503,6 +503,12 @@ DST_LISTEN_SOCKET = 5008
DD_ANIMATION_LENGTH = 16 DD_ANIMATION_LENGTH = 16
DD_OFFSET_FROM_CENTER = 4 DD_OFFSET_FROM_CENTER = 4
# Qubes related
QUBES_SRC_LISTEN_SOCKET = 2063
QUBES_DST_LISTEN_SOCKET = 2064
SOCKET_BUFFER_SIZE = 4096
QUBES_RX_IP_ADDR_FILE = 'rx_ip_addr'
# Field lengths # Field lengths
ENCODED_BOOLEAN_LENGTH = 1 ENCODED_BOOLEAN_LENGTH = 1
ENCODED_BYTE_LENGTH = 1 ENCODED_BYTE_LENGTH = 1

View File

@ -79,7 +79,7 @@ def process_command(ts: 'datetime',
header, cmd = separate_header(cmd_packet.assemble_command_packet(), ENCRYPTED_COMMAND_HEADER_LENGTH) header, cmd = separate_header(cmd_packet.assemble_command_packet(), ENCRYPTED_COMMAND_HEADER_LENGTH)
no = None no = None
# Keyword Function to run ( Parameters ) # Header Function to run ( Parameters )
# -------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------
d = {LOCAL_KEY_RDY: (local_key_rdy, ts, window_list, contact_list ), d = {LOCAL_KEY_RDY: (local_key_rdy, ts, window_list, contact_list ),
WIN_ACTIVITY: (win_activity, window_list ), WIN_ACTIVITY: (win_activity, window_list ),

View File

@ -93,9 +93,7 @@ def process_assembled_file(ts: 'datetime', # Timestamp last receiv
if len(file_key) != SYMMETRIC_KEY_LENGTH: if len(file_key) != SYMMETRIC_KEY_LENGTH:
raise SoftError("Error: Received file had an invalid key.") raise SoftError("Error: Received file had an invalid key.")
decrypt_and_store_file( decrypt_and_store_file(ts, file_ct, file_key, file_name, onion_pub_key, nick, window_list, settings)
ts, file_ct, file_key, file_name, onion_pub_key, nick, window_list, settings
)
def decrypt_and_store_file(ts: 'datetime', # Timestamp of received packet def decrypt_and_store_file(ts: 'datetime', # Timestamp of received packet
@ -146,8 +144,7 @@ def new_file(ts: 'datetime', # Timestamp o
contact = contact_list.get_contact_by_pub_key(onion_pub_key) contact = contact_list.get_contact_by_pub_key(onion_pub_key)
if not contact.file_reception: if not contact.file_reception:
raise SoftError( raise SoftError(f"Alert! Discarded file from {contact.nick} as file reception for them is disabled.", bold=True)
f"Alert! Discarded file from {contact.nick} as file reception for them is disabled.", bold=True)
k = onion_pub_key + blake2b(file_ct) # Dictionary key k = onion_pub_key + blake2b(file_ct) # Dictionary key

View File

@ -77,7 +77,7 @@ def protect_kdk(kdk: bytes) -> None:
def process_local_key_buffer(kdk: bytes, def process_local_key_buffer(kdk: bytes,
l_queue: 'local_key_queue' l_queue: 'local_key_queue'
) -> Tuple[datetime, bytes]: ) -> Tuple[datetime, bytes]:
"""Check if the kdk was for a packet further ahead in the queue.""" """Check if the KDK was for a packet further ahead in the queue."""
buffer = [] # type: List[Tuple[datetime, bytes]] buffer = [] # type: List[Tuple[datetime, bytes]]
while l_queue.qsize() > 0: while l_queue.qsize() > 0:
tup = l_queue.get() # type: Tuple[datetime, bytes] tup = l_queue.get() # type: Tuple[datetime, bytes]
@ -113,7 +113,7 @@ def decrypt_local_key(ts: 'datetime',
) -> Tuple['datetime', bytes]: ) -> Tuple['datetime', bytes]:
"""Decrypt local key packet.""" """Decrypt local key packet."""
while True: while True:
kdk = get_b58_key(B58_LOCAL_KEY, settings) kdk = get_b58_key(B58_LOCAL_KEY, settings)
kdk_hash = blake2b(kdk) kdk_hash = blake2b(kdk)
# Check if the key was an old one. # Check if the key was an old one.

View File

@ -90,22 +90,22 @@ def decrypt_assembly_packet(packet: bytes, # Assembly packet cip
While all message datagrams have been implicitly assumed to have While all message datagrams have been implicitly assumed to have
originated from some contact until this point, to prevent the originated from some contact until this point, to prevent the
possibility of existential forgeries, the origin of message will be possibility of existential forgeries, the origin of the message will
validated at this point with the cryptographic Poly1305-tag. be validated at this point with the cryptographic Poly1305-tag.
As per the cryptographic doom principle, the message will not be As per the cryptographic doom principle, the message won't be even
even decrypted unless the Poly1305 tag of the ciphertext is valid. decrypted unless the Poly1305 tag of the ciphertext is valid.
This function also authentication of packets that handle control This function also authenticates packets that handle control flow of
flow of the Receiver program. Like messages, command datagrams have the Receiver program. Like messages, command datagrams have been
been implicitly assumed to be commands until this point. However, implicitly assumed to be commands until this point. However, unless
unless the Poly1305-tag of the purported command is found to be valid the Poly1305-tag of the purported command is found to be valid with
with the forward secret local key, it will not be even decrypted, the forward secret local key, it will not be even decrypted, let
let alone processed. alone processed.
""" """
ct_harac, ct_assemby_packet = separate_header(packet, header_length=HARAC_CT_LENGTH) ct_harac, ct_assembly_packet = separate_header(packet, header_length=HARAC_CT_LENGTH)
cmd_win = window_list.get_command_window() cmd_win = window_list.get_command_window()
command = onion_pub_key == LOCAL_PUBKEY command = onion_pub_key == LOCAL_PUBKEY
p_type = "command" if command else "packet" p_type = "command" if command else "packet"
direction = "from" if command or (origin == ORIGIN_CONTACT_HEADER) else "sent to" direction = "from" if command or (origin == ORIGIN_CONTACT_HEADER) else "sent to"
@ -142,7 +142,7 @@ def decrypt_assembly_packet(packet: bytes, # Assembly packet cip
# Decrypt packet # Decrypt packet
try: try:
assembly_packet = auth_and_decrypt(ct_assemby_packet, message_key) assembly_packet = auth_and_decrypt(ct_assembly_packet, message_key)
except nacl.exceptions.CryptoError: except nacl.exceptions.CryptoError:
raise SoftError(f"Warning! Received {p_type} {direction} {nick} had an invalid MAC.", raise SoftError(f"Warning! Received {p_type} {direction} {nick} had an invalid MAC.",
window=cmd_win) window=cmd_win)

View File

@ -32,19 +32,17 @@ import requests
from cryptography.hazmat.primitives.asymmetric.x448 import X448PublicKey, X448PrivateKey from cryptography.hazmat.primitives.asymmetric.x448 import X448PublicKey, X448PrivateKey
from src.common.encoding import b58encode, int_to_bytes, onion_address_to_pub_key, pub_key_to_onion_address from src.common.encoding import (b58encode, int_to_bytes, onion_address_to_pub_key, pub_key_to_onion_address,
from src.common.encoding import pub_key_to_short_address pub_key_to_short_address)
from src.common.exceptions import SoftError from src.common.exceptions import SoftError
from src.common.misc import ignored, separate_header, split_byte_string, validate_onion_addr from src.common.misc import ignored, separate_header, split_byte_string, validate_onion_addr
from src.common.output import m_print, print_key, rp_print from src.common.output import m_print, print_key, rp_print
from src.common.statics import (ACCOUNT_SEND_QUEUE, from src.common.statics import (ACCOUNT_SEND_QUEUE, CLIENT_OFFLINE_THRESHOLD, CONTACT_MGMT_QUEUE, CONTACT_REQ_QUEUE,
CLIENT_OFFLINE_THRESHOLD, CONTACT_MGMT_QUEUE, CONTACT_REQ_QUEUE, C_REQ_MGMT_QUEUE, C_REQ_MGMT_QUEUE, C_REQ_STATE_QUEUE, DATAGRAM_HEADER_LENGTH, DST_MESSAGE_QUEUE,
C_REQ_STATE_QUEUE, DATAGRAM_HEADER_LENGTH, DST_MESSAGE_QUEUE, FILE_DATAGRAM_HEADER, GROUP_ID_LENGTH, GROUP_MGMT_QUEUE, GROUP_MSG_EXIT_GROUP_HEADER,
FILE_DATAGRAM_HEADER, GROUP_ID_LENGTH, GROUP_MGMT_QUEUE, GROUP_MSG_INVITE_HEADER, GROUP_MSG_JOIN_HEADER, GROUP_MSG_MEMBER_ADD_HEADER,
GROUP_MSG_EXIT_GROUP_HEADER, GROUP_MSG_INVITE_HEADER, GROUP_MSG_JOIN_HEADER, GROUP_MSG_MEMBER_REM_HEADER, GROUP_MSG_QUEUE, MESSAGE_DATAGRAM_HEADER,
GROUP_MSG_MEMBER_ADD_HEADER, GROUP_MSG_MEMBER_REM_HEADER, GROUP_MSG_QUEUE, ONION_SERVICE_PUBLIC_KEY_LENGTH, ORIGIN_CONTACT_HEADER, PUB_KEY_SEND_QUEUE,
MESSAGE_DATAGRAM_HEADER, ONION_SERVICE_PUBLIC_KEY_LENGTH,
ORIGIN_CONTACT_HEADER, PUB_KEY_SEND_QUEUE,
PUBLIC_KEY_DATAGRAM_HEADER, RELAY_CLIENT_MAX_DELAY, RELAY_CLIENT_MIN_DELAY, PUBLIC_KEY_DATAGRAM_HEADER, RELAY_CLIENT_MAX_DELAY, RELAY_CLIENT_MIN_DELAY,
RP_ADD_CONTACT_HEADER, RP_REMOVE_CONTACT_HEADER, TFC_PUBLIC_KEY_LENGTH, RP_ADD_CONTACT_HEADER, RP_REMOVE_CONTACT_HEADER, TFC_PUBLIC_KEY_LENGTH,
TOR_DATA_QUEUE, UNIT_TEST_QUEUE, URL_TOKEN_LENGTH, URL_TOKEN_QUEUE) TOR_DATA_QUEUE, UNIT_TEST_QUEUE, URL_TOKEN_LENGTH, URL_TOKEN_QUEUE)
@ -406,9 +404,9 @@ def process_group_management_message(data: bytes,
def c_req_manager(queues: 'QueueDict', unit_test: bool = False) -> None: def c_req_manager(queues: 'QueueDict', unit_test: bool = False) -> None:
"""Manage incoming contact requests.""" """Manage displayed contact requests."""
existing_contacts = [] # type: List[bytes] existing_contacts = [] # type: List[bytes]
contact_requests = [] # type: List[bytes] displayed_requests = [] # type: List[bytes]
request_queue = queues[CONTACT_REQ_QUEUE] request_queue = queues[CONTACT_REQ_QUEUE]
contact_queue = queues[C_REQ_MGMT_QUEUE] contact_queue = queues[C_REQ_MGMT_QUEUE]
@ -431,7 +429,7 @@ def c_req_manager(queues: 'QueueDict', unit_test: bool = False) -> None:
onion_pub_key = onion_address_to_pub_key(purp_onion_address) onion_pub_key = onion_address_to_pub_key(purp_onion_address)
if onion_pub_key in existing_contacts: if onion_pub_key in existing_contacts:
continue continue
if onion_pub_key in contact_requests: if onion_pub_key in displayed_requests:
continue continue
if show_requests: if show_requests:
@ -439,7 +437,7 @@ def c_req_manager(queues: 'QueueDict', unit_test: bool = False) -> None:
m_print([f"{ts} - New contact request from an unknown TFC account:", purp_onion_address], box=True) m_print([f"{ts} - New contact request from an unknown TFC account:", purp_onion_address], box=True)
account_queue.put(purp_onion_address) account_queue.put(purp_onion_address)
contact_requests.append(onion_pub_key) displayed_requests.append(onion_pub_key)
if unit_test and queues[UNIT_TEST_QUEUE].qsize() != 0: if unit_test and queues[UNIT_TEST_QUEUE].qsize() != 0:
break break

View File

@ -72,7 +72,7 @@ def process_command(command: bytes,
"""Select function for received Relay Program command.""" """Select function for received Relay Program command."""
header, command = separate_header(command, UNENCRYPTED_COMMAND_HEADER_LENGTH) header, command = separate_header(command, UNENCRYPTED_COMMAND_HEADER_LENGTH)
# Keyword Function to run ( Parameters ) # Header Function to run ( Parameters )
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
function_d = {UNENCRYPTED_SCREEN_CLEAR: (clear_windows, gateway, ), function_d = {UNENCRYPTED_SCREEN_CLEAR: (clear_windows, gateway, ),
UNENCRYPTED_SCREEN_RESET: (reset_windows, gateway, ), UNENCRYPTED_SCREEN_RESET: (reset_windows, gateway, ),
@ -81,8 +81,8 @@ def process_command(command: bytes,
UNENCRYPTED_EC_RATIO: (change_ec_ratio, command, gateway, ), UNENCRYPTED_EC_RATIO: (change_ec_ratio, command, gateway, ),
UNENCRYPTED_BAUDRATE: (change_baudrate, command, gateway, ), UNENCRYPTED_BAUDRATE: (change_baudrate, command, gateway, ),
UNENCRYPTED_MANAGE_CONTACT_REQ: (manage_contact_req, command, queues), UNENCRYPTED_MANAGE_CONTACT_REQ: (manage_contact_req, command, queues),
UNENCRYPTED_ADD_NEW_CONTACT: (add_contact, command, False, queues), UNENCRYPTED_ADD_NEW_CONTACT: (add_contact, command, queues, False ),
UNENCRYPTED_ADD_EXISTING_CONTACT: (add_contact, command, True, queues), UNENCRYPTED_ADD_EXISTING_CONTACT: (add_contact, command, queues, True ),
UNENCRYPTED_REM_CONTACT: (remove_contact, command, queues), UNENCRYPTED_REM_CONTACT: (remove_contact, command, queues),
UNENCRYPTED_ONION_SERVICE_DATA: (add_onion_data, command, queues), UNENCRYPTED_ONION_SERVICE_DATA: (add_onion_data, command, queues),
UNENCRYPTED_ACCOUNT_CHECK: (compare_accounts, command, queues), UNENCRYPTED_ACCOUNT_CHECK: (compare_accounts, command, queues),
@ -185,8 +185,8 @@ def manage_contact_req(command: bytes,
def add_contact(command: bytes, def add_contact(command: bytes,
queues: 'QueueDict',
existing: bool, existing: bool,
queues: 'QueueDict'
) -> None: ) -> None:
"""Add clients to Relay Program. """Add clients to Relay Program.
@ -232,9 +232,9 @@ def add_onion_data(command: bytes, queues: 'QueueDict') -> None:
existing_public_keys = public_key_list[no_pending:] existing_public_keys = public_key_list[no_pending:]
for onion_pub_key in pending_public_keys: for onion_pub_key in pending_public_keys:
add_contact(onion_pub_key, False, queues) add_contact(onion_pub_key, queues, existing=False)
for onion_pub_key in existing_public_keys: for onion_pub_key in existing_public_keys:
add_contact(onion_pub_key, True, queues) add_contact(onion_pub_key, queues, existing=True)
manage_contact_req(allow_req_byte, queues, notify=False) manage_contact_req(allow_req_byte, queues, notify=False)
queues[ONION_KEY_QUEUE].put((os_private_key, confirmation_code)) queues[ONION_KEY_QUEUE].put((os_private_key, confirmation_code))

View File

@ -196,34 +196,31 @@ def show_value_diffs(value_type: str,
purp_value: str, purp_value: str,
local_test: bool local_test: bool
) -> None: ) -> None:
"""Compare purported value with correct value.""" """Show differences between purported value and correct value."""
# Pad with underscores to denote missing chars # Pad with underscores to denote missing chars
while len(purp_value) < ENCODED_B58_PUB_KEY_LENGTH: while len(purp_value) < ENCODED_B58_PUB_KEY_LENGTH:
purp_value += '_' purp_value += '_'
replace_l = '' rep_arrows = ''
purported = '' purported = ''
for c1, c2 in zip(purp_value, true_value): for c1, c2 in zip(purp_value, true_value):
if c1 == c2: rep_arrows += ' ' if c1 == c2 else ''
replace_l += ' ' purported += c1
purported += c1
else:
replace_l += ''
purported += c1
message_list = [f"Source Computer received an invalid {value_type}.", message_list = [f"Source Computer received an invalid {value_type}.",
"See arrows below that point to correct characters."] "See arrows below that point to correct characters."]
if local_test: if local_test:
m_print(message_list + ['', purported, replace_l, true_value], box=True) m_print(message_list + ['', purported, rep_arrows, true_value], box=True)
else: else:
purported = ' '.join(split_string(purported, item_len=7)) purported = ' '.join(split_string(purported, item_len=7))
replace_l = ' '.join(split_string(replace_l, item_len=7)) rep_arrows = ' '.join(split_string(rep_arrows, item_len=7))
true_value = ' '.join(split_string(true_value, item_len=7)) true_value = ' '.join(split_string(true_value, item_len=7))
m_print(message_list + ['', m_print(message_list + ['',
B58_PUBLIC_KEY_GUIDE, B58_PUBLIC_KEY_GUIDE,
purported, purported,
replace_l, rep_arrows,
true_value, true_value,
B58_PUBLIC_KEY_GUIDE], box=True) B58_PUBLIC_KEY_GUIDE], box=True)

View File

@ -19,8 +19,8 @@ You should have received a copy of the GNU General Public License
along with TFC. If not, see <https://www.gnu.org/licenses/>. along with TFC. If not, see <https://www.gnu.org/licenses/>.
""" """
import hmac
import logging import logging
import secrets
import typing import typing
from io import BytesIO from io import BytesIO
@ -67,7 +67,10 @@ def validate_url_token(purp_url_token: str,
# True if a matching shared secret was found in pub_key_dict. # True if a matching shared secret was found in pub_key_dict.
valid_url_token = False valid_url_token = False
for url_token in pub_key_dict: for url_token in pub_key_dict:
valid_url_token |= hmac.compare_digest(purp_url_token, url_token) try:
valid_url_token |= secrets.compare_digest(purp_url_token, url_token)
except TypeError:
valid_url_token |= False
return valid_url_token return valid_url_token

View File

@ -24,8 +24,7 @@ import typing
from typing import Any, Dict, List, Tuple, Union from typing import Any, Dict, List, Tuple, Union
from src.common.encoding import bytes_to_int, pub_key_to_short_address from src.common.encoding import b85encode, bytes_to_int, int_to_bytes, pub_key_to_short_address
from src.common.encoding import int_to_bytes, b85encode
from src.common.exceptions import SoftError from src.common.exceptions import SoftError
from src.common.misc import ignored, separate_header, split_byte_string from src.common.misc import ignored, separate_header, split_byte_string
from src.common.output import rp_print from src.common.output import rp_print
@ -217,7 +216,8 @@ def process_add_or_group_remove_member(ts: 'datetime',
header_str: str, header_str: str,
group_id: bytes, group_id: bytes,
messages_to_flask: 'Queue[Tuple[Union[bytes, str], bytes]]', messages_to_flask: 'Queue[Tuple[Union[bytes, str], bytes]]',
remaining: List[bytes], removable: List[bytes] remaining: List[bytes],
removable: List[bytes]
) -> None: ) -> None:
"""Process group add or remove member packet.""" """Process group add or remove member packet."""
packet_str = header_str + b85encode(group_id + b"".join(removable)) packet_str = header_str + b85encode(group_id + b"".join(removable))

View File

@ -79,7 +79,7 @@ def process_command(user_input: 'UserInput',
Select function based on the first keyword of the Select function based on the first keyword of the
issued command, and pass relevant parameters to it. issued command, and pass relevant parameters to it.
""" """
# Keyword Function to run ( Parameters ) # Command Function to run ( Parameters )
# ----------------------------------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------------------------------
d = {'about': (print_about, ), d = {'about': (print_about, ),
'add': (add_new_contact, contact_list, group_list, settings, queues, onion_service ), 'add': (add_new_contact, contact_list, group_list, settings, queues, onion_service ),

View File

@ -212,7 +212,7 @@ def new_local_key(contact_list: 'ContactList',
key, csprng(), key, csprng(),
hek, csprng())) hek, csprng()))
# Notify Receiver that confirmation code was successfully entered # Notify Receiver Program that confirmation code was successfully entered
queue_command(LOCAL_KEY_RDY, settings, queues) queue_command(LOCAL_KEY_RDY, settings, queues)
m_print("Successfully completed the local key exchange.", bold=True, tail_clear=True, delay=1, head=1) m_print("Successfully completed the local key exchange.", bold=True, tail_clear=True, delay=1, head=1)
@ -319,7 +319,7 @@ def start_key_exchange(onion_pub_key: bytes, # Public key of contact's
dh_shared_key = X448.shared_key(tfc_private_key_user, tfc_public_key_contact) dh_shared_key = X448.shared_key(tfc_private_key_user, tfc_public_key_contact)
tx_mk, rx_mk, tx_hk, rx_hk, tx_fp, rx_fp \ tx_mk, rx_mk, tx_hk, rx_hk, tx_fp, rx_fp \
= X448.derive_keys(dh_shared_key, tfc_public_key_user, tfc_public_key_contact) = X448.derive_subkeys(dh_shared_key, tfc_public_key_user, tfc_public_key_contact)
kex_status = validate_contact_fingerprint(tx_fp, rx_fp) kex_status = validate_contact_fingerprint(tx_fp, rx_fp)
@ -577,10 +577,10 @@ def store_keys_on_removable_drive(ct_tag: bytes, # Encrypted PS
settings: 'Settings', # Settings object settings: 'Settings', # Settings object
) -> None: ) -> None:
"""Store keys for contact on a removable media.""" """Store keys for contact on a removable media."""
trunc_addr = pub_key_to_short_address(onion_pub_key)
while True: while True:
trunc_addr = pub_key_to_short_address(onion_pub_key) store_d = ask_path_gui(f"Select removable media for {nick}", settings)
store_d = ask_path_gui(f"Select removable media for {nick}", settings) f_name = f"{store_d}/{onion_service.user_short_address}.psk - Give to {trunc_addr}"
f_name = f"{store_d}/{onion_service.user_short_address}.psk - Give to {trunc_addr}"
try: try:
with open(f_name, "wb+") as f: with open(f_name, "wb+") as f:
@ -624,4 +624,4 @@ def rxp_load_psk(window: 'TxWindow',
print_on_previous_line(reps=4, delay=2) print_on_previous_line(reps=4, delay=2)
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt):
raise SoftError("PSK verification aborted.", tail_clear=True, delay=1, head=2) raise SoftError("PSK install verification aborted.", tail_clear=True, delay=1, head=2)

View File

@ -25,8 +25,8 @@ import typing
from typing import Any, Dict, Optional, Tuple, Union from typing import Any, Dict, Optional, Tuple, Union
from src.common.misc import ignored from src.common.misc import ignored
from src.common.statics import (C_N_HEADER, NOISE_PACKET_BUFFER, PADDING_LENGTH, P_N_HEADER, from src.common.statics import (C_N_HEADER, NOISE_PACKET_BUFFER, PADDING_LENGTH, P_N_HEADER, TM_NOISE_COMMAND_QUEUE,
TM_NOISE_COMMAND_QUEUE, TM_NOISE_PACKET_QUEUE) TM_NOISE_PACKET_QUEUE)
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from multiprocessing import Queue from multiprocessing import Queue

View File

@ -39,8 +39,8 @@ import nacl.utils
from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat
from src.common.crypto import argon2_kdf, auth_and_decrypt, blake2b, byte_padding, check_kernel_version, csprng from src.common.crypto import (argon2_kdf, auth_and_decrypt, blake2b, byte_padding, check_kernel_version, csprng,
from src.common.crypto import encrypt_and_sign, rm_padding_bytes, X448 encrypt_and_sign, rm_padding_bytes, X448)
from src.common.statics import (ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM, ARGON2_MIN_TIME_COST, from src.common.statics import (ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM, ARGON2_MIN_TIME_COST,
ARGON2_SALT_LENGTH, BLAKE2_DIGEST_LENGTH, BLAKE2_DIGEST_LENGTH_MAX, ARGON2_SALT_LENGTH, BLAKE2_DIGEST_LENGTH, BLAKE2_DIGEST_LENGTH_MAX,
BLAKE2_DIGEST_LENGTH_MIN, BLAKE2_KEY_LENGTH_MAX, BLAKE2_PERSON_LENGTH_MAX, BLAKE2_DIGEST_LENGTH_MIN, BLAKE2_KEY_LENGTH_MAX, BLAKE2_PERSON_LENGTH_MAX,
@ -125,7 +125,7 @@ class TestBLAKE2bWrapper(unittest.TestCase):
These tests ensure the BLAKE2b implementation detects invalid These tests ensure the BLAKE2b implementation detects invalid
parameters. parameters.
""" """
def setUp(self) -> None: def setUp(self) -> None:
"""Pre-test actions.""" """Pre-test actions."""
self.test_string = b'test_string' self.test_string = b'test_string'
@ -184,7 +184,7 @@ class TestArgon2KDF(unittest.TestCase):
output of the argon2_cffi library to the output of the command-line output of the argon2_cffi library to the output of the command-line
utility under those input parameters. utility under those input parameters.
[1] https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03#section-6.3 [1] https://tools.ietf.org/html/draft-irtf-cfrg-argon2-09#section-5.3
[2] https://github.com/P-H-C/phc-winner-argon2#command-line-utility [2] https://github.com/P-H-C/phc-winner-argon2#command-line-utility
""" """
@ -339,8 +339,8 @@ class TestX448(unittest.TestCase):
The pyca/cryptography library does not provide bindings for the The pyca/cryptography library does not provide bindings for the
OpenSSL's X448 internals, but both KATs are done by OpenSSL tests: OpenSSL's X448 internals, but both KATs are done by OpenSSL tests:
https://github.com/openssl/openssl/blob/master/test/curve448_internal_test.c#L654 https://github.com/openssl/openssl/blob/master/test/curve448_internal_test.c#L655
https://github.com/openssl/openssl/blob/master/test/curve448_internal_test.c#L668 https://github.com/openssl/openssl/blob/master/test/curve448_internal_test.c#L669
""" """
sk_alice = bytes.fromhex( sk_alice = bytes.fromhex(
'9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d' '9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d'
@ -446,23 +446,23 @@ class TestX448(unittest.TestCase):
self.assertEqual(shared_secret1, blake2b(TestX448.shared_secret)) self.assertEqual(shared_secret1, blake2b(TestX448.shared_secret))
self.assertEqual(shared_secret2, blake2b(TestX448.shared_secret)) self.assertEqual(shared_secret2, blake2b(TestX448.shared_secret))
def test_non_unique_keys_raise_critical_error(self) -> None: def test_non_unique_subkeys_raise_critical_error(self) -> None:
# Setup # Setup
shared_key = os.urandom(SYMMETRIC_KEY_LENGTH) shared_key = os.urandom(SYMMETRIC_KEY_LENGTH)
tx_public_key = os.urandom(TFC_PUBLIC_KEY_LENGTH) tx_public_key = os.urandom(TFC_PUBLIC_KEY_LENGTH)
# Test # Test
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
X448.derive_keys(shared_key, tx_public_key, tx_public_key) X448.derive_subkeys(shared_key, tx_public_key, tx_public_key)
def test_x448_key_derivation(self) -> None: def test_x448_subkey_derivation(self) -> None:
# Setup # Setup
shared_key = os.urandom(SYMMETRIC_KEY_LENGTH) shared_key = os.urandom(SYMMETRIC_KEY_LENGTH)
tx_public_key = os.urandom(TFC_PUBLIC_KEY_LENGTH) tx_public_key = os.urandom(TFC_PUBLIC_KEY_LENGTH)
rx_public_key = os.urandom(TFC_PUBLIC_KEY_LENGTH) rx_public_key = os.urandom(TFC_PUBLIC_KEY_LENGTH)
# Test # Test
key_set = X448.derive_keys(shared_key, tx_public_key, rx_public_key) key_set = X448.derive_subkeys(shared_key, tx_public_key, rx_public_key)
# Test that correct number of keys were returned # Test that correct number of keys were returned
self.assertEqual(len(key_set), 6) self.assertEqual(len(key_set), 6)
@ -486,7 +486,7 @@ class TestXChaCha20Poly1305(unittest.TestCase):
ciphertext and tag. ciphertext and tag.
IETF test vectors: IETF test vectors:
https://tools.ietf.org/html/draft-irtf-cfrg-xchacha-01#appendix-A.1 https://tools.ietf.org/html/draft-irtf-cfrg-xchacha-03#appendix-A.3
Libsodium test vectors: Libsodium test vectors:
Message: https://github.com/jedisct1/libsodium/blob/master/test/default/aead_xchacha20poly1305.c#L22 Message: https://github.com/jedisct1/libsodium/blob/master/test/default/aead_xchacha20poly1305.c#L22
@ -500,38 +500,28 @@ class TestXChaCha20Poly1305(unittest.TestCase):
""" """
ietf_plaintext = bytes.fromhex( ietf_plaintext = bytes.fromhex(
'4c 61 64 69 65 73 20 61 6e 64 20 47 65 6e 74 6c' '4c616469657320616e642047656e746c656d656e206f662074686520636c6173'
'65 6d 65 6e 20 6f 66 20 74 68 65 20 63 6c 61 73' '73206f66202739393a204966204920636f756c64206f6666657220796f75206f'
'73 20 6f 66 20 27 39 39 3a 20 49 66 20 49 20 63' '6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73'
'6f 75 6c 64 20 6f 66 66 65 72 20 79 6f 75 20 6f' '637265656e20776f756c642062652069742e')
'6e 6c 79 20 6f 6e 65 20 74 69 70 20 66 6f 72 20'
'74 68 65 20 66 75 74 75 72 65 2c 20 73 75 6e 73'
'63 72 65 65 6e 20 77 6f 75 6c 64 20 62 65 20 69'
'74 2e')
ietf_ad = bytes.fromhex( ietf_ad = bytes.fromhex(
'50 51 52 53 c0 c1 c2 c3 c4 c5 c6 c7') '50515253c0c1c2c3c4c5c6c7')
ietf_key = bytes.fromhex( ietf_key = bytes.fromhex(
'80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f' '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f')
'90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f')
ietf_nonce = bytes.fromhex( ietf_nonce = bytes.fromhex(
'40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f' '404142434445464748494a4b4c4d4e4f5051525354555657')
'50 51 52 53 54 55 56 57')
ietf_ciphertext = bytes.fromhex( ietf_ciphertext = bytes.fromhex(
'bd 6d 17 9d 3e 83 d4 3b 95 76 57 94 93 c0 e9 39' 'bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb'
'57 2a 17 00 25 2b fa cc be d2 90 2c 21 39 6c bb' '731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b452'
'73 1c 7f 1b 0b 4a a6 44 0b f3 a8 2f 4e da 7e 39' '2f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff9'
'ae 64 c6 70 8c 54 c2 16 cb 96 b7 2e 12 13 b4 52' '21f9664c97637da9768812f615c68b13b52e')
'2f 8c 9b a4 0d b5 d9 45 b1 1b 69 b9 82 c1 bb 9e'
'3f 3f ac 2b c3 69 48 8f 76 b2 38 35 65 d3 ff f9'
'21 f9 66 4c 97 63 7d a9 76 88 12 f6 15 c6 8b 13'
'b5 2e')
ietf_tag = bytes.fromhex( ietf_tag = bytes.fromhex(
'c0:87:59:24:c1:c7:98:79:47:de:af:d8:78:0a:cf:49'.replace(':', '')) 'c0875924c1c7987947deafd8780acf49')
nonce_ct_tag_ietf = ietf_nonce + ietf_ciphertext + ietf_tag nonce_ct_tag_ietf = ietf_nonce + ietf_ciphertext + ietf_tag
@ -712,7 +702,7 @@ class TestCSPRNG(unittest.TestCase):
https://github.com/smuellerDD/lrng/tree/master/test https://github.com/smuellerDD/lrng/tree/master/test
The report on the statistical tests of the LRNG can be found from The report on the statistical tests of the LRNG can be found from
Chapter 3 (pp.26-48) of the whitepaper: Chapter 3 (pp.30-46) of the white paper:
https://www.chronox.de/lrng/doc/lrng.pdf https://www.chronox.de/lrng/doc/lrng.pdf
Further analysis of the LRNG can be found from Chapters 4-8 Further analysis of the LRNG can be found from Chapters 4-8
@ -742,7 +732,7 @@ class TestCSPRNG(unittest.TestCase):
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
csprng() csprng()
def test_subceeding_hash_function_min_digest_size_raises_critical_error(self) -> None: def test_subceding_hash_function_min_digest_size_raises_critical_error(self) -> None:
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
csprng(BLAKE2_DIGEST_LENGTH_MIN-1) csprng(BLAKE2_DIGEST_LENGTH_MIN-1)

View File

@ -303,7 +303,7 @@ class TestTFCUnencryptedDatabase(unittest.TestCase):
self.assertEqual(self.database.load_database(), data_old) self.assertEqual(self.database.load_database(), data_old)
self.assertFalse(os.path.isfile(self.database.database_temp)) self.assertFalse(os.path.isfile(self.database.database_temp))
def test_load_database_prefers_valid_temp_database(self) -> None: def test_load_database_prioritizes_valid_temp_database(self) -> None:
# Setup # Setup
data_old = os.urandom(MASTERKEY_DB_SIZE) data_old = os.urandom(MASTERKEY_DB_SIZE)
checksummed_old = data_old + blake2b(data_old) checksummed_old = data_old + blake2b(data_old)
@ -432,7 +432,7 @@ class TestMessageLog(unittest.TestCase):
self.message_log.c.execute(f"""INSERT INTO log_entries (log_entry) VALUES (?)""", self.message_log.c.execute(f"""INSERT INTO log_entries (log_entry) VALUES (?)""",
(os.urandom(LOG_ENTRY_LENGTH),)) (os.urandom(LOG_ENTRY_LENGTH),))
# Test that TFC reopens closed database on write # Test closed database is re-opened during write
data = os.urandom(LOG_ENTRY_LENGTH) data = os.urandom(LOG_ENTRY_LENGTH)
self.assertIsNone(self.message_log.insert_log_entry(data)) self.assertIsNone(self.message_log.insert_log_entry(data))

View File

@ -322,12 +322,12 @@ class TestContactList(TFCTestCase):
self.assertFalse(self.contact_list.has_contacts()) self.assertFalse(self.contact_list.has_contacts())
def test_has_only_pending_contacts(self) -> None: def test_has_only_pending_contacts(self) -> None:
# Change all to pending # Change all contacts' kex status to pending
for contact in self.contact_list.get_list_of_contacts(): for contact in self.contact_list.get_list_of_contacts():
contact.kex_status = KEX_STATUS_PENDING contact.kex_status = KEX_STATUS_PENDING
self.assertTrue(self.contact_list.has_only_pending_contacts()) self.assertTrue(self.contact_list.has_only_pending_contacts())
# Change one from pending # Change one kex status to unverified
alice = self.contact_list.get_contact_by_address_or_nick('Alice') alice = self.contact_list.get_contact_by_address_or_nick('Alice')
alice.kex_status = KEX_STATUS_UNVERIFIED alice.kex_status = KEX_STATUS_UNVERIFIED
self.assertFalse(self.contact_list.has_only_pending_contacts()) self.assertFalse(self.contact_list.has_only_pending_contacts())

View File

@ -170,7 +170,7 @@ class TestGroupList(TFCTestCase):
+ self.settings.max_number_of_groups * self.single_member_data_len + self.settings.max_number_of_groups * self.single_member_data_len
+ POLY1305_TAG_LENGTH) + POLY1305_TAG_LENGTH)
# Reduce setting values from 20 to 10 # Reduce group database setting values from 20 to 10
self.settings.max_number_of_groups = 10 self.settings.max_number_of_groups = 10
self.settings.max_number_of_group_members = 10 self.settings.max_number_of_group_members = 10
@ -244,29 +244,29 @@ class TestGroupList(TFCTestCase):
def test_add_group(self) -> None: def test_add_group(self) -> None:
members = [create_contact('Laura')] members = [create_contact('Laura')]
self.group_list.add_group('test_group_12', bytes(GROUP_ID_LENGTH), False, False, members) self.group_list.add_group('test_group_12', bytes(GROUP_ID_LENGTH), False, False, members)
self.group_list.add_group('test_group_12', bytes(GROUP_ID_LENGTH), False, True, members) self.group_list.add_group('test_group_12', bytes(GROUP_ID_LENGTH), False, True, members)
self.assertTrue(self.group_list.get_group('test_group_12').notifications) self.assertTrue(self.group_list.get_group('test_group_12').notifications)
self.assertEqual(len(self.group_list), len(self.group_names)+1) self.assertEqual(len(self.group_list), len(self.group_names)+1)
def test_remove_group_by_name(self) -> None: def test_remove_group_by_name(self) -> None:
self.assertEqual(len(self.group_list), len(self.group_names)) self.assertEqual(len(self.group_list), len(self.group_names))
# Remove non-existing group # Test removing a non-existing group
self.assertIsNone(self.group_list.remove_group_by_name('test_group_12')) self.assertIsNone(self.group_list.remove_group_by_name('test_group_12'))
self.assertEqual(len(self.group_list), len(self.group_names)) self.assertEqual(len(self.group_list), len(self.group_names))
# Remove existing group # Test removing an existing group
self.assertIsNone(self.group_list.remove_group_by_name('test_group_11')) self.assertIsNone(self.group_list.remove_group_by_name('test_group_11'))
self.assertEqual(len(self.group_list), len(self.group_names)-1) self.assertEqual(len(self.group_list), len(self.group_names)-1)
def test_remove_group_by_id(self) -> None: def test_remove_group_by_id(self) -> None:
self.assertEqual(len(self.group_list), len(self.group_names)) self.assertEqual(len(self.group_list), len(self.group_names))
# Remove non-existing group # Test removing a non-existing group
self.assertIsNone(self.group_list.remove_group_by_id(group_name_to_group_id('test_group_12'))) self.assertIsNone(self.group_list.remove_group_by_id(group_name_to_group_id('test_group_12')))
self.assertEqual(len(self.group_list), len(self.group_names)) self.assertEqual(len(self.group_list), len(self.group_names))
# Remove existing group # Test removing an existing group
self.assertIsNone(self.group_list.remove_group_by_id(group_name_to_group_id('test_group_11'))) self.assertIsNone(self.group_list.remove_group_by_id(group_name_to_group_id('test_group_11')))
self.assertEqual(len(self.group_list), len(self.group_names)-1) self.assertEqual(len(self.group_list), len(self.group_names)-1)

View File

@ -160,26 +160,26 @@ class TestKeyList(unittest.TestCase):
new_key = bytes(SYMMETRIC_KEY_LENGTH) new_key = bytes(SYMMETRIC_KEY_LENGTH)
self.keylist.keysets = [create_keyset(LOCAL_ID)] self.keylist.keysets = [create_keyset(LOCAL_ID)]
# Check that KeySet exists and that its keys are different # Check that KeySet exists and that its keys are different from the new ones
self.assertNotEqual(self.keylist.keysets[0].rx_hk, new_key) self.assertNotEqual(self.keylist.keysets[0].rx_hk, new_key)
# Replace existing KeySet # Replace the existing KeySet
self.assertIsNone(self.keylist.add_keyset(LOCAL_PUBKEY, self.assertIsNone(self.keylist.add_keyset(LOCAL_PUBKEY,
new_key, new_key, new_key, new_key,
new_key, new_key)) new_key, new_key))
# Check that new KeySet replaced the old one # 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].onion_pub_key, LOCAL_PUBKEY)
self.assertEqual(self.keylist.keysets[0].rx_hk, new_key) self.assertEqual(self.keylist.keysets[0].rx_hk, new_key)
def test_remove_keyset(self) -> None: def test_remove_keyset(self) -> None:
# Test KeySet for Bob exists # Test that the KeySet for Bob exists
self.assertTrue(self.keylist.has_keyset(nick_to_pub_key('Bob'))) self.assertTrue(self.keylist.has_keyset(nick_to_pub_key('Bob')))
# Remove KeySet for Bob # Remove the KeySet for Bob
self.assertIsNone(self.keylist.remove_keyset(nick_to_pub_key('Bob'))) self.assertIsNone(self.keylist.remove_keyset(nick_to_pub_key('Bob')))
# Test KeySet was removed # Test that the KeySet was removed
self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('Bob'))) self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('Bob')))
@mock.patch('builtins.input', side_effect=['test_password']) @mock.patch('builtins.input', side_effect=['test_password'])
@ -190,18 +190,18 @@ class TestKeyList(unittest.TestCase):
queues = gen_queue_dict() queues = gen_queue_dict()
def queue_delayer() -> None: def queue_delayer() -> None:
"""Place packet to queue after timer runs out.""" """Place packet to the key management queue after timer runs out."""
time.sleep(0.1) time.sleep(0.1)
queues[KEY_MANAGEMENT_QUEUE].put(master_key2.master_key) queues[KEY_MANAGEMENT_QUEUE].put(master_key2.master_key)
threading.Thread(target=queue_delayer).start() threading.Thread(target=queue_delayer).start()
# Test that new key is different from existing one # Test that the new key is different from the existing one
self.assertNotEqual(key, self.master_key.master_key) self.assertNotEqual(key, self.master_key.master_key)
# Change master key # Change the master key
self.assertIsNone(self.keylist.change_master_key(queues)) self.assertIsNone(self.keylist.change_master_key(queues))
# Test that master key has changed # Test that the master key was changed
self.assertEqual(self.keylist.master_key.master_key, key) self.assertEqual(self.keylist.master_key.master_key, key)
self.assertEqual(self.keylist.database.database_key, key) self.assertEqual(self.keylist.database.database_key, key)
@ -254,20 +254,20 @@ class TestKeyList(unittest.TestCase):
# Setup # Setup
queues = gen_queue_dict() queues = gen_queue_dict()
# Test that KeySet for David does not exist # Test that the KeySet for David does not exist
self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('David'))) self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('David')))
# Test adding KeySet # Test adding the KeySet for David
self.assertIsNone(self.keylist.manage(queues, KDB_ADD_ENTRY_HEADER, nick_to_pub_key('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),
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'))) self.assertTrue(self.keylist.has_keyset(nick_to_pub_key('David')))
# Test removing KeySet # Test removing David's KeySet
self.assertIsNone(self.keylist.manage(queues, KDB_REMOVE_ENTRY_HEADER, nick_to_pub_key('David'))) 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'))) self.assertFalse(self.keylist.has_keyset(nick_to_pub_key('David')))
# Test changing master key # Test changing the master key
new_key = SYMMETRIC_KEY_LENGTH * b'\x01' new_key = SYMMETRIC_KEY_LENGTH * b'\x01'
self.assertNotEqual(self.master_key.master_key, new_key) self.assertNotEqual(self.master_key.master_key, new_key)
@ -278,7 +278,7 @@ class TestKeyList(unittest.TestCase):
self.assertEqual(self.keylist.master_key.master_key, new_key) self.assertEqual(self.keylist.master_key.master_key, new_key)
self.assertEqual(self.keylist.database.database_key, new_key) self.assertEqual(self.keylist.database.database_key, new_key)
# Test invalid KeyList management command raises Critical Error # Test an invalid KeyList management command raises CriticalError
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
self.keylist.manage(queues, 'invalid_key', None) self.keylist.manage(queues, 'invalid_key', None)

View File

@ -41,8 +41,8 @@ from src.common.statics import (CLEAR_ENTIRE_SCREEN, CURSOR_LEFT_UP_CORNER,
WIN_TYPE_GROUP) WIN_TYPE_GROUP)
from tests.mock_classes import create_contact, GroupList, MasterKey, RxWindow, Settings from tests.mock_classes import create_contact, GroupList, MasterKey, RxWindow, Settings
from tests.utils import assembly_packet_creator, cd_unit_test, cleanup, group_name_to_group_id, nick_to_pub_key from tests.utils import (assembly_packet_creator, cd_unit_test, cleanup, group_name_to_group_id, nick_to_pub_key,
from tests.utils import nick_to_short_address, tear_queues, TFCTestCase, gen_queue_dict nick_to_short_address, tear_queues, TFCTestCase, gen_queue_dict)
TIMESTAMP_BYTES = bytes.fromhex('08ceae02') TIMESTAMP_BYTES = bytes.fromhex('08ceae02')
STATIC_TIMESTAMP = bytes_to_timestamp(TIMESTAMP_BYTES).strftime('%H:%M:%S.%f')[:-TIMESTAMP_LENGTH] STATIC_TIMESTAMP = bytes_to_timestamp(TIMESTAMP_BYTES).strftime('%H:%M:%S.%f')[:-TIMESTAMP_LENGTH]
@ -68,7 +68,7 @@ class TestLogWriterLoop(unittest.TestCase):
queues = gen_queue_dict() queues = gen_queue_dict()
def queue_delayer() -> None: def queue_delayer() -> None:
"""Place messages to queue one at a time.""" """Place messages to the logging queue one at a time."""
for p in [(nick_to_pub_key('Alice'), M_S_HEADER + bytes(PADDING_LENGTH), False, False, master_key), for p in [(nick_to_pub_key('Alice'), M_S_HEADER + bytes(PADDING_LENGTH), False, False, master_key),
(None, C_S_HEADER + bytes(PADDING_LENGTH), True, False, master_key), (None, C_S_HEADER + bytes(PADDING_LENGTH), True, False, master_key),
(nick_to_pub_key('Alice'), P_N_HEADER + bytes(PADDING_LENGTH), True, True, master_key), (nick_to_pub_key('Alice'), P_N_HEADER + bytes(PADDING_LENGTH), True, True, master_key),
@ -101,7 +101,7 @@ class TestLogWriterLoop(unittest.TestCase):
queues[TRAFFIC_MASKING_QUEUE].put(True) queues[TRAFFIC_MASKING_QUEUE].put(True)
def queue_delayer() -> None: def queue_delayer() -> None:
"""Place messages to queue one at a time.""" """Place messages to the logging queue one at a time."""
for p in [(nick_to_pub_key('Alice'), M_S_HEADER + bytes(PADDING_LENGTH), False, False, master_key), for p in [(nick_to_pub_key('Alice'), M_S_HEADER + bytes(PADDING_LENGTH), False, False, master_key),
(None, C_S_HEADER + bytes(PADDING_LENGTH), True, False, master_key), (None, C_S_HEADER + bytes(PADDING_LENGTH), True, False, master_key),
(nick_to_pub_key('Alice'), F_S_HEADER + bytes(PADDING_LENGTH), True, True, master_key), (nick_to_pub_key('Alice'), F_S_HEADER + bytes(PADDING_LENGTH), True, True, master_key),
@ -131,7 +131,7 @@ class TestLogWriterLoop(unittest.TestCase):
queues = gen_queue_dict() queues = gen_queue_dict()
def queue_delayer() -> None: def queue_delayer() -> None:
"""Place messages to queue one at a time.""" """Place messages to the logging queue one at a time."""
for p in [(None, C_S_HEADER + bytes(PADDING_LENGTH), True, False, master_key), for p in [(None, C_S_HEADER + bytes(PADDING_LENGTH), True, False, master_key),
(nick_to_pub_key('Alice'), M_S_HEADER + bytes(PADDING_LENGTH), False, False, master_key), (nick_to_pub_key('Alice'), M_S_HEADER + bytes(PADDING_LENGTH), False, False, master_key),
(nick_to_pub_key('Alice'), F_S_HEADER + bytes(PADDING_LENGTH), True, True, master_key)]: (nick_to_pub_key('Alice'), F_S_HEADER + bytes(PADDING_LENGTH), True, True, master_key)]:
@ -139,7 +139,7 @@ class TestLogWriterLoop(unittest.TestCase):
queues[LOG_PACKET_QUEUE].put(p) queues[LOG_PACKET_QUEUE].put(p)
time.sleep(SLEEP_DELAY) time.sleep(SLEEP_DELAY)
queues[LOGFILE_MASKING_QUEUE].put(True) # Start logging noise packets queues[LOGFILE_MASKING_QUEUE].put(True) # Start logging of noise packets
time.sleep(SLEEP_DELAY) time.sleep(SLEEP_DELAY)
for _ in range(2): for _ in range(2):
@ -171,7 +171,7 @@ class TestLogWriterLoop(unittest.TestCase):
noise_tuple = (nick_to_pub_key('Alice'), P_N_HEADER + bytes(PADDING_LENGTH), True, True, master_key) noise_tuple = (nick_to_pub_key('Alice'), P_N_HEADER + bytes(PADDING_LENGTH), True, True, master_key)
def queue_delayer() -> None: def queue_delayer() -> None:
"""Place packets to log into queue after delay.""" """Place packets to log into the log queue after delay."""
for _ in range(5): for _ in range(5):
queues[LOG_PACKET_QUEUE].put(noise_tuple) # Not logged because logging_state is False by default queues[LOG_PACKET_QUEUE].put(noise_tuple) # Not logged because logging_state is False by default
time.sleep(SLEEP_DELAY) time.sleep(SLEEP_DELAY)
@ -267,7 +267,7 @@ class TestAccessHistoryAndPrintLogs(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_missing_log_file_raises_se(self) -> None: def test_missing_log_file_raises_soft_error(self) -> None:
# Setup # Setup
os.remove(self.log_file) os.remove(self.log_file)
@ -443,8 +443,8 @@ Log file of message(s) sent to group test_group
group=self.group, group=self.group,
type_print='group') type_print='group')
# Add an assembly packet sequence sent to contact Alice in group containing cancel packet. # Add an assembly packet sequence sent to contact Alice in group
# Access_logs should skip this. # containing cancel packet. Access_logs should skip this.
packets = assembly_packet_creator(MESSAGE, self.msg, group_id=group_name_to_group_id('test_group')) packets = assembly_packet_creator(MESSAGE, self.msg, group_id=group_name_to_group_id('test_group'))
packets = packets[2:] + [M_C_HEADER + bytes(PADDING_LENGTH)] packets = packets[2:] + [M_C_HEADER + bytes(PADDING_LENGTH)]
for p in packets: for p in packets:
@ -461,8 +461,8 @@ Log file of message(s) sent to group test_group
for p in assembly_packet_creator(MESSAGE, 'This is a short group message', group_id=GROUP_ID_LENGTH * b'1'): for p in assembly_packet_creator(MESSAGE, 'This is a short group message', group_id=GROUP_ID_LENGTH * b'1'):
write_log_entry(p, nick_to_pub_key('Alice'), self.tfc_log_database) write_log_entry(p, nick_to_pub_key('Alice'), self.tfc_log_database)
# Add messages to Alice and Charlie in group. # Add messages to Alice and Charlie in group. Add duplicate
# Add duplicate of outgoing message that should be skipped by access_logs. # of outgoing message that should be skipped by access_logs.
for p in assembly_packet_creator(MESSAGE, self.msg, group_id=group_name_to_group_id('test_group')): for p in assembly_packet_creator(MESSAGE, self.msg, group_id=group_name_to_group_id('test_group')):
write_log_entry(p, nick_to_pub_key('Alice'), self.tfc_log_database) write_log_entry(p, nick_to_pub_key('Alice'), self.tfc_log_database)
write_log_entry(p, nick_to_pub_key('Alice'), self.tfc_log_database, origin=ORIGIN_CONTACT_HEADER) write_log_entry(p, nick_to_pub_key('Alice'), self.tfc_log_database, origin=ORIGIN_CONTACT_HEADER)
@ -546,7 +546,7 @@ class TestReEncrypt(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_missing_log_database_raises_se(self) -> None: def test_missing_log_database_raises_soft_error(self) -> None:
# Setup # Setup
os.remove(self.log_file) os.remove(self.log_file)
@ -632,7 +632,7 @@ class TestRemoveLog(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_missing_log_file_raises_se(self) -> None: def test_missing_log_file_raises_soft_error(self) -> None:
# Setup # Setup
os.remove(self.file_name) os.remove(self.file_name)

View File

@ -135,15 +135,53 @@ class TestMasterKey(TFCTestCase):
self.assertIsInstance(master_key.master_key, bytes) self.assertIsInstance(master_key.master_key, bytes)
@mock.patch('src.common.db_masterkey.MasterKey.timed_key_derivation', @mock.patch('src.common.db_masterkey.MasterKey.timed_key_derivation',
MagicMock(side_effect= [(KL*b'a', 0.01)] MagicMock(side_effect= [(KL*b'a', 3.5)] # Early exit to create the object.
+ [(KL*b'a', 4.1)] # Test1: Make key derivation too slow so it returns with time_cost 1
+ [(KL*b'a', 2.0)] # Test2: Second key derivation time sentinel
+ [(KL*b'a', 4.1)]
+ [(KL*b'a', 0.1)] # Test3: Complete binary search with search end
+ [(KL*b'a', 6.0)]
+ 7 * [(KL*b'a', 2.5)]))
@mock.patch('os.popen', return_value=MagicMock(
read=MagicMock(return_value=MagicMock(splitlines=MagicMock(return_value=["MemAvailable 10240"])))))
@mock.patch('getpass.getpass', side_effect=['generate'])
@mock.patch('builtins.input', side_effect=[''])
@mock.patch('os.system', return_value=None)
@mock.patch('time.sleep', return_value=None)
def test_determine_time_cost(self, *_: Any) -> None:
master_key = MasterKey(self.operation, local_test=True)
# Test1: Sentinel returns immediately if MAX_KEY_DERIVATION_TIME is exceeded
time_cost, kd_time, _ = master_key.determine_time_cost("password", 8*b'salt', memory_cost=512, parallelism=1)
self.assertEqual(time_cost, 1)
self.assertEqual(kd_time, 4.1)
# Test2: Second key derivation time sentinel
time_cost, kd_time, _ = master_key.determine_time_cost("password", 8 * b'salt', memory_cost=512, parallelism=1)
self.assertEqual(time_cost, 2)
self.assertEqual(kd_time, 4.1)
# Test3: Complete binary search with search end
time_cost, kd_time, _ = master_key.determine_time_cost("password", 8 * b'salt', memory_cost=512, parallelism=1)
self.assertEqual(time_cost, 40)
self.assertEqual(kd_time, 2.5)
@mock.patch('src.common.db_masterkey.MasterKey.timed_key_derivation',
MagicMock(side_effect= [(KL*b'a', 4.1)]
+ [(KL*b'a', 3.5)]
+ [(KL*b'a', 0.01)]
+ 100 * [(KL*b'b', 5.0)] + 100 * [(KL*b'b', 5.0)]
+ 2 * [(KL*b'a', 2.5)] + 2 * [(KL*b'a', 2.5)]
+ [(KL*b'a', 3.0)])) + [(KL*b'a', 3.1)]))
@mock.patch('os.path.isfile', side_effect=[False, True]) @mock.patch('os.popen', return_value=MagicMock(
@mock.patch('getpass.getpass', side_effect=input_list) read=MagicMock(return_value=MagicMock(splitlines=MagicMock(return_value=["MemAvailable 10240"])))))
@mock.patch('time.sleep', return_value=None) @mock.patch('getpass.getpass', side_effect=['generate'])
def test_kd_binary_search(self, *_: Any) -> None: @mock.patch('builtins.input', side_effect=[''])
MasterKey(self.operation, local_test=True) @mock.patch('os.system', return_value=None)
@mock.patch('time.sleep', return_value=None)
def test_determine_memory_cost(self, *_: Any) -> None:
master_key = MasterKey(self.operation, local_test=True)
master_key.determine_memory_cost("password", 8*b'salt', time_cost=1, memory_cost=1024, parallelism=1)
@mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 0.01) @mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 0.01)
@mock.patch('src.common.db_masterkey.MAX_KEY_DERIVATION_TIME', 0.1) @mock.patch('src.common.db_masterkey.MAX_KEY_DERIVATION_TIME', 0.1)

View File

@ -113,8 +113,8 @@ class TestSettings(TFCTestCase):
self.assert_se("Error: Invalid setting value 'True'.", self.assert_se("Error: Invalid setting value 'True'.",
self.settings.change_setting, 'tm_static_delay', 'True', *self.args) self.settings.change_setting, 'tm_static_delay', 'True', *self.args)
self.assertIsNone(self.settings.change_setting('traffic_masking', 'True', *self.args)) self.assertIsNone(self.settings.change_setting('traffic_masking', 'True', *self.args))
self.assertIsNone(self.settings.change_setting('max_number_of_group_members', '100', *self.args)) self.assertIsNone(self.settings.change_setting('max_number_of_group_members', '100', *self.args))
@mock.patch('builtins.input', side_effect=['No', 'Yes']) @mock.patch('builtins.input', side_effect=['No', 'Yes'])
def test_validate_key_value_pair(self, _: Any) -> None: def test_validate_key_value_pair(self, _: Any) -> None:

View File

@ -25,10 +25,10 @@ import unittest
from datetime import datetime from datetime import datetime
from src.common.encoding import b58encode, bool_to_bytes, double_to_bytes, str_to_bytes, int_to_bytes from src.common.encoding import (b58encode, bool_to_bytes, double_to_bytes, str_to_bytes, int_to_bytes,
from src.common.encoding import b58decode, bytes_to_bool, bytes_to_double, bytes_to_str, bytes_to_int b58decode, bytes_to_bool, bytes_to_double, bytes_to_str, bytes_to_int,
from src.common.encoding import onion_address_to_pub_key, unicode_padding, pub_key_to_short_address, b85encode onion_address_to_pub_key, unicode_padding, pub_key_to_short_address, b85encode,
from src.common.encoding import pub_key_to_onion_address, rm_padding_str, bytes_to_timestamp, b10encode pub_key_to_onion_address, rm_padding_str, bytes_to_timestamp, b10encode)
from src.common.statics import (ENCODED_BOOLEAN_LENGTH, ENCODED_FLOAT_LENGTH, ENCODED_INTEGER_LENGTH, from src.common.statics import (ENCODED_BOOLEAN_LENGTH, ENCODED_FLOAT_LENGTH, ENCODED_INTEGER_LENGTH,
FINGERPRINT_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH, PADDED_UTF32_STR_LENGTH, FINGERPRINT_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH, PADDED_UTF32_STR_LENGTH,
PADDING_LENGTH, SYMMETRIC_KEY_LENGTH, TFC_PUBLIC_KEY_LENGTH, TRUNC_ADDRESS_LENGTH) PADDING_LENGTH, SYMMETRIC_KEY_LENGTH, TFC_PUBLIC_KEY_LENGTH, TRUNC_ADDRESS_LENGTH)

View File

@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License
along with TFC. If not, see <https://www.gnu.org/licenses/>. along with TFC. If not, see <https://www.gnu.org/licenses/>.
""" """
import base64
import os import os
import unittest import unittest
import socket import socket
@ -34,7 +35,8 @@ from src.common.crypto import blake2b
from src.common.gateway import gateway_loop, Gateway, GatewaySettings from src.common.gateway import gateway_loop, Gateway, GatewaySettings
from src.common.misc import ensure_dir from src.common.misc import ensure_dir
from src.common.reed_solomon import RSCodec from src.common.reed_solomon import RSCodec
from src.common.statics import DIR_USER_DATA, GATEWAY_QUEUE, NC, PACKET_CHECKSUM_LENGTH, RX, TX from src.common.statics import (DIR_USER_DATA, GATEWAY_QUEUE, NC, PACKET_CHECKSUM_LENGTH, QUBES_RX_IP_ADDR_FILE,
RX, TX, US_BYTE)
from tests.mock_classes import Settings from tests.mock_classes import Settings
from tests.utils import cd_unit_test, cleanup, gen_queue_dict, tear_queues, TFCTestCase from tests.utils import cd_unit_test, cleanup, gen_queue_dict, tear_queues, TFCTestCase
@ -55,7 +57,7 @@ class TestGatewayLoop(unittest.TestCase):
@mock.patch('multiprocessing.connection.Listener', @mock.patch('multiprocessing.connection.Listener',
return_value=MagicMock(accept=lambda: MagicMock(recv=MagicMock(return_value='message')))) return_value=MagicMock(accept=lambda: MagicMock(recv=MagicMock(return_value='message'))))
def test_loop(self, _: Any) -> None: def test_loop(self, _: Any) -> None:
gateway = Gateway(operation=RX, local_test=True, dd_sockets=False) gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
self.assertIsNone(gateway_loop(self.queues, gateway, unit_test=True)) self.assertIsNone(gateway_loop(self.queues, gateway, unit_test=True))
data = self.queues[GATEWAY_QUEUE].get() data = self.queues[GATEWAY_QUEUE].get()
@ -79,7 +81,7 @@ class TestGatewaySerial(TFCTestCase):
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0']]) @mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0']])
@mock.patch('builtins.input', side_effect=['Yes']) @mock.patch('builtins.input', side_effect=['Yes'])
def test_search_and_establish_serial(self, *_: Any) -> None: def test_search_and_establish_serial(self, *_: Any) -> None:
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False) gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
self.assertIsInstance(gateway.rs, RSCodec) self.assertIsInstance(gateway.rs, RSCodec)
self.assertIs(gateway.tx_serial, gateway.rx_serial) self.assertIs(gateway.tx_serial, gateway.rx_serial)
@ -87,16 +89,16 @@ class TestGatewaySerial(TFCTestCase):
@mock.patch('serial.Serial', side_effect=SerialException) @mock.patch('serial.Serial', side_effect=SerialException)
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0']]) @mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0']])
@mock.patch('builtins.input', side_effect=['Yes']) @mock.patch('builtins.input', side_effect=['Yes'])
def test_serialexception_during_establish_exists(self, *_: Any) -> None: def test_serial_exception_during_establish_exists(self, *_: Any) -> None:
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
Gateway(operation=RX, local_test=False, dd_sockets=False) Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('serial.Serial', return_value=MagicMock(write=MagicMock(side_effect=[SerialException, None]))) @mock.patch('serial.Serial', return_value=MagicMock(write=MagicMock(side_effect=[SerialException, None])))
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0'], ['ttyUSB0']]) @mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0'], ['ttyUSB0']])
@mock.patch('builtins.input', side_effect=['Yes']) @mock.patch('builtins.input', side_effect=['Yes'])
def test_write_serial_(self, *_: Any) -> None: def test_write_serial_(self, *_: Any) -> None:
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False) gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
self.assertIsNone(gateway.write(b"message")) self.assertIsNone(gateway.write(b"message"))
@mock.patch("time.sleep", return_value=None) @mock.patch("time.sleep", return_value=None)
@ -106,7 +108,7 @@ class TestGatewaySerial(TFCTestCase):
@mock.patch("builtins.input", side_effect=["Yes"]) @mock.patch("builtins.input", side_effect=["Yes"])
def test_serial_uninitialized_serial_interface_for_read_raises_critical_error(self, *_) -> None: def test_serial_uninitialized_serial_interface_for_read_raises_critical_error(self, *_) -> None:
# Setup # Setup
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False) gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
gateway.rx_serial = None gateway.rx_serial = None
# Test # Test
@ -119,7 +121,7 @@ class TestGatewaySerial(TFCTestCase):
@mock.patch("builtins.input", side_effect=["Yes"]) @mock.patch("builtins.input", side_effect=["Yes"])
def test_serial_uninitialized_socket_interface_for_read_raises_critical_error(self, *_) -> None: def test_serial_uninitialized_socket_interface_for_read_raises_critical_error(self, *_) -> None:
# Setup # Setup
gateway = Gateway(operation=RX, local_test=True, dd_sockets=False) gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
gateway.rx_socket = None gateway.rx_socket = None
# Test # Test
@ -133,7 +135,7 @@ class TestGatewaySerial(TFCTestCase):
@mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"], ["ttyUSB0"]]) @mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"], ["ttyUSB0"]])
@mock.patch("builtins.input", side_effect=["Yes"]) @mock.patch("builtins.input", side_effect=["Yes"])
def test_read_socket(self, *_) -> None: def test_read_socket(self, *_) -> None:
gateway = Gateway(operation=RX, local_test=True, dd_sockets=False) gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
data = gateway.read() data = gateway.read()
self.assertEqual(data, b"12") self.assertEqual(data, b"12")
@ -144,7 +146,7 @@ class TestGatewaySerial(TFCTestCase):
@mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"], ["ttyUSB0"]]) @mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"], ["ttyUSB0"]])
@mock.patch("builtins.input", side_effect=["Yes"]) @mock.patch("builtins.input", side_effect=["Yes"])
def test_read_serial(self, *_) -> None: def test_read_serial(self, *_) -> None:
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False) gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
data = gateway.read() data = gateway.read()
self.assertEqual(data, b"12") self.assertEqual(data, b"12")
@ -153,29 +155,26 @@ class TestGatewaySerial(TFCTestCase):
@mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"]]) @mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"]])
@mock.patch("builtins.input", side_effect=["Yes"]) @mock.patch("builtins.input", side_effect=["Yes"])
def test_add_error_correction(self, *_) -> None: def test_add_error_correction(self, *_) -> None:
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False) gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
packet = b"packet" packet = b"packet"
# Test BLAKE2b based checksum # Test BLAKE2b based checksum
gateway.settings.session_serial_error_correction = 0 gateway.settings.session_serial_error_correction = 0
self.assertEqual( self.assertEqual(gateway.add_error_correction(packet),
gateway.add_error_correction(packet), packet + blake2b(packet, digest_size=PACKET_CHECKSUM_LENGTH))
packet + blake2b(packet, digest_size=PACKET_CHECKSUM_LENGTH),
)
# Test Reed-Solomon erasure code # Test Reed-Solomon erasure code
gateway.settings.session_serial_error_correction = 5 gateway.settings.session_serial_error_correction = 5
gateway.rs = RSCodec(gateway.settings.session_serial_error_correction) gateway.rs = RSCodec(gateway.settings.session_serial_error_correction)
self.assertEqual( self.assertEqual(gateway.add_error_correction(packet),
gateway.add_error_correction(packet), gateway.rs.encode(packet) gateway.rs.encode(packet))
)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('serial.Serial', return_value=MagicMock()) @mock.patch('serial.Serial', return_value=MagicMock())
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0']]) @mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0']])
@mock.patch('builtins.input', side_effect=['Yes']) @mock.patch('builtins.input', side_effect=['Yes'])
def test_detect_errors(self, *_: Any) -> None: def test_detect_errors(self, *_: Any) -> None:
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False) gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
packet = b'packet' packet = b'packet'
# Test BLAKE2b based checksum # Test BLAKE2b based checksum
@ -183,7 +182,7 @@ class TestGatewaySerial(TFCTestCase):
self.assertEqual(gateway.detect_errors(gateway.add_error_correction(packet)), self.assertEqual(gateway.detect_errors(gateway.add_error_correction(packet)),
packet) packet)
# Test unrecoverable error raises FR # Test unrecoverable error raises SoftError
self.assert_se("Warning! Received packet had an invalid checksum.", self.assert_se("Warning! Received packet had an invalid checksum.",
gateway.detect_errors, 300 * b'a') gateway.detect_errors, 300 * b'a')
@ -193,16 +192,30 @@ class TestGatewaySerial(TFCTestCase):
self.assertEqual(gateway.detect_errors(gateway.add_error_correction(packet)), self.assertEqual(gateway.detect_errors(gateway.add_error_correction(packet)),
packet) packet)
# Test unrecoverable error raises FR # Test unrecoverable error raises SoftError
self.assert_se("Error: Reed-Solomon failed to correct errors in the received packet.", self.assert_se("Error: Reed-Solomon failed to correct errors in the received packet.",
gateway.detect_errors, 300 * b'a') gateway.detect_errors, 300 * b'a')
# Qubes
# Test with B58 encoding
gateway.settings.qubes = True
packet_with_error_correction = base64.b85encode(gateway.add_error_correction(packet))
self.assertEqual(gateway.detect_errors(packet_with_error_correction), packet)
# Test invalid B85 encoding raises SoftError
packet_with_error_correction = base64.b85encode(gateway.add_error_correction(packet))
packet_with_error_correction += b'\x00'
self.assert_se("Error: Received packet had invalid Base85 encoding.",
gateway.detect_errors, packet_with_error_correction)
gateway.settings.qubes = False
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('serial.Serial', return_value=MagicMock()) @mock.patch('serial.Serial', return_value=MagicMock())
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0'], [''], ['ttyUSB0'], ['ttyS0'], ['']]) @mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0'], [''], ['ttyUSB0'], ['ttyS0'], ['']])
@mock.patch('builtins.input', side_effect=['Yes']) @mock.patch('builtins.input', side_effect=['Yes'])
def test_search_serial_interfaces(self, *_: Any) -> None: def test_search_serial_interfaces(self, *_: Any) -> None:
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False) gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
interface = gateway.search_serial_interface() interface = gateway.search_serial_interface()
self.assertEqual(interface, '/dev/ttyUSB0') self.assertEqual(interface, '/dev/ttyUSB0')
@ -220,39 +233,38 @@ class TestGatewaySerial(TFCTestCase):
@mock.patch('multiprocessing.connection.Client', MagicMock()) @mock.patch('multiprocessing.connection.Client', MagicMock())
@mock.patch('multiprocessing.connection.Listener', MagicMock()) @mock.patch('multiprocessing.connection.Listener', MagicMock())
def test_establish_local_testing_gateway(self, *_: Any) -> None: def test_establish_local_testing_gateway(self, *_: Any) -> None:
gateway = Gateway(operation=NC, local_test=True, dd_sockets=False) gateway = Gateway(operation=NC, local_test=True, dd_sockets=False, qubes=False)
self.assertIsInstance(gateway.rs, RSCodec) self.assertIsInstance(gateway.rs, RSCodec)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('multiprocessing.connection.Client', MagicMock(side_effect=KeyboardInterrupt)) @mock.patch('multiprocessing.connection.Client', MagicMock(side_effect=KeyboardInterrupt))
def test_keyboard_interrupt_exits(self, *_: Any) -> None: def test_keyboard_interrupt_exits(self, *_: Any) -> None:
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
Gateway(operation=TX, local_test=True, dd_sockets=False) Gateway(operation=TX, local_test=True, dd_sockets=False, qubes=False)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('multiprocessing.connection.Client', MagicMock( @mock.patch('multiprocessing.connection.Client', MagicMock(
side_effect=[socket.error, ConnectionRefusedError, MagicMock()])) side_effect=[socket.error, ConnectionRefusedError, MagicMock()]))
def test_socket_client(self, *_: Any) -> None: def test_socket_client(self, *_: Any) -> None:
gateway = Gateway(operation=TX, local_test=True, dd_sockets=False) gateway = Gateway(operation=TX, local_test=True, dd_sockets=False, qubes=False)
self.assertIsInstance(gateway, Gateway) self.assertIsInstance(gateway, Gateway)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('multiprocessing.connection.Listener', MagicMock( @mock.patch('multiprocessing.connection.Listener', MagicMock(
side_effect=[MagicMock(), KeyboardInterrupt])) side_effect=[MagicMock(), KeyboardInterrupt]))
def test_socket_server(self, *_: Any) -> None: def test_socket_server(self, *_: Any) -> None:
gateway = Gateway(operation=RX, local_test=True, dd_sockets=False) gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
self.assertIsInstance(gateway, Gateway) self.assertIsInstance(gateway, Gateway)
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
Gateway(operation=RX, local_test=True, dd_sockets=False) Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('multiprocessing.connection.Listener', return_value=MagicMock( @mock.patch('multiprocessing.connection.Listener', return_value=MagicMock(
accept=lambda: MagicMock(recv=MagicMock(side_effect=[KeyboardInterrupt, b'data', EOFError])))) accept=lambda: MagicMock(recv=MagicMock(side_effect=[KeyboardInterrupt, b'data', EOFError]))))
def test_local_testing_read(self, *_: Any) -> None: def test_local_testing_read(self, *_: Any) -> None:
gateway = Gateway(operation=RX, local_test=True, dd_sockets=False) gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
self.assertEqual(gateway.read(), b'data') self.assertEqual(gateway.read(), b'data')
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
gateway.read() gateway.read()
@ -260,13 +272,61 @@ class TestGatewaySerial(TFCTestCase):
@mock.patch('multiprocessing.connection.Client', return_value=MagicMock( @mock.patch('multiprocessing.connection.Client', return_value=MagicMock(
send=MagicMock(side_effect=[None, BrokenPipeError]))) send=MagicMock(side_effect=[None, BrokenPipeError])))
def test_local_testing_write(self, *_: Any) -> None: def test_local_testing_write(self, *_: Any) -> None:
gateway = Gateway(operation=TX, local_test=True, dd_sockets=False) gateway = Gateway(operation=TX, local_test=True, dd_sockets=False, qubes=False)
self.assertIsNone(gateway.write(b'data')) self.assertIsNone(gateway.write(b'data'))
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
gateway.write(b'data') gateway.write(b'data')
# Qubes
@mock.patch('time.sleep', return_value=None)
@mock.patch('socket.socket', MagicMock(return_value=MagicMock(
recv=MagicMock(side_effect=[EOFError, b'data', US_BYTE]))))
def test_qubes_socket_server(self, *_: Any) -> None:
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=True)
self.assertIsInstance(gateway, Gateway)
self.assertEqual(gateway.read(), b'data')
@mock.patch('time.sleep', return_value=None)
@mock.patch('socket.socket', MagicMock(return_value=MagicMock(
recv=MagicMock(side_effect=[EOFError, b'data', US_BYTE]))))
def test_qubes_socket_server_raises_critical_error_if_interface_is_not_initialized(self, *_: Any) -> None:
# Setup
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=True)
gateway.rxq_socket = None
# Test
with self.assertRaises(SystemExit):
self.assertEqual(gateway.read(), b'data')
@mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
@mock.patch('socket.socket', MagicMock())
def test_qubes_socket_client(self, *_: Any) -> None:
gateway = Gateway(operation=TX, local_test=False, dd_sockets=False, qubes=True)
self.assertIsInstance(gateway, Gateway)
self.assertIsNone(gateway.write(b'data'))
@mock.patch('time.sleep', return_value=None)
@mock.patch('socket.socket', MagicMock())
def test_qubes_auto_config_from_file(self, *_: Any) -> None:
# Setup
test_ip = '10.137.0.17'
open(QUBES_RX_IP_ADDR_FILE, 'w+').write(test_ip)
# Test
self.assertTrue(os.path.isfile(QUBES_RX_IP_ADDR_FILE))
gateway = Gateway(operation=TX, local_test=False, dd_sockets=False, qubes=True)
self.assertEqual(gateway.settings.rx_udp_ip, test_ip)
self.assertFalse(os.path.isfile(QUBES_RX_IP_ADDR_FILE))
@mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
@mock.patch('socket.socket', MagicMock(return_value=MagicMock(connect=MagicMock(side_effect=[socket.error]))))
def test_socket_error_raises_critical_error(self, *_: Any) -> None:
gateway = Gateway(operation=TX, local_test=False, dd_sockets=False, qubes=True)
with self.assertRaises(SystemExit):
gateway.get_local_ip_addr()
class TestGatewaySettings(TFCTestCase): class TestGatewaySettings(TFCTestCase):
@ -278,7 +338,8 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": 19200, "serial_baudrate": 19200,
"serial_error_correction": 5, "serial_error_correction": 5,
"use_serial_usb_adapter": true, "use_serial_usb_adapter": true,
"built_in_serial_interface": "ttyS0" "built_in_serial_interface": "ttyS0",
"rx_udp_ip": ""
}""" }"""
def tearDown(self) -> None: def tearDown(self) -> None:
@ -288,11 +349,11 @@ class TestGatewaySettings(TFCTestCase):
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyS0'], ['ttyUSB0'], ['ttyS0']]) @mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyS0'], ['ttyUSB0'], ['ttyS0']])
@mock.patch('builtins.input', side_effect=['yes', 'yes', 'no', 'no']) @mock.patch('builtins.input', side_effect=['yes', 'yes', 'no', 'no'])
def test_gateway_setup(self, *_: Any) -> None: def test_gateway_setup(self, *_: Any) -> None:
settings = GatewaySettings(operation=TX, local_test=False, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=False, dd_sockets=True, qubes=False)
self.assertIsNone(settings.setup()) self.assertIsNone(settings.setup())
def test_store_and_load_of_settings(self) -> None: def test_store_and_load_of_settings(self) -> None:
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assertTrue(os.path.isfile(f'{DIR_USER_DATA}/{TX}_serial_settings.json')) self.assertTrue(os.path.isfile(f'{DIR_USER_DATA}/{TX}_serial_settings.json'))
self.assertEqual(settings.serial_baudrate, 19200) self.assertEqual(settings.serial_baudrate, 19200)
@ -301,7 +362,7 @@ class TestGatewaySettings(TFCTestCase):
settings.use_serial_usb_adapter = False settings.use_serial_usb_adapter = False
self.assertIsNone(settings.store_settings()) self.assertIsNone(settings.store_settings())
settings2 = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings2 = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assertEqual(settings2.serial_baudrate, 115200) self.assertEqual(settings2.serial_baudrate, 115200)
self.assertEqual(settings.use_serial_usb_adapter, False) self.assertEqual(settings.use_serial_usb_adapter, False)
@ -315,14 +376,16 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": 9600, "serial_baudrate": 9600,
"serial_error_correction": 1, "serial_error_correction": 1,
"use_serial_usb_adapter": false, "use_serial_usb_adapter": false,
"built_in_serial_interface": "ttyS0" "built_in_serial_interface": "ttyS0",
"rx_udp_ip": "10.137.0.17"
}""") }""")
# Test # Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assertEqual(settings.serial_baudrate, 9600) self.assertEqual(settings.serial_baudrate, 9600)
self.assertEqual(settings.serial_error_correction, 1) self.assertEqual(settings.serial_error_correction, 1)
self.assertEqual(settings.use_serial_usb_adapter, False) self.assertEqual(settings.use_serial_usb_adapter, False)
self.assertEqual(settings.built_in_serial_interface, 'ttyS0') self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
self.assertEqual(settings.rx_udp_ip, '10.137.0.17')
def test_missing_values_are_set_to_default_and_database_is_overwritten(self) -> None: def test_missing_values_are_set_to_default_and_database_is_overwritten(self) -> None:
# Setup # Setup
@ -335,7 +398,7 @@ class TestGatewaySettings(TFCTestCase):
"relay_usb_serial_adapter": false "relay_usb_serial_adapter": false
}""") }""")
# Test # Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assertEqual(settings.serial_baudrate, 19200) self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 1) self.assertEqual(settings.serial_error_correction, 1)
self.assertEqual(settings.use_serial_usb_adapter, False) self.assertEqual(settings.use_serial_usb_adapter, False)
@ -352,7 +415,7 @@ class TestGatewaySettings(TFCTestCase):
}""") }""")
# Test # Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assertEqual(settings.serial_baudrate, 19200) self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 5) self.assertEqual(settings.serial_error_correction, 5)
self.assertEqual(settings.use_serial_usb_adapter, True) self.assertEqual(settings.use_serial_usb_adapter, True)
@ -372,10 +435,11 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": 19201, "serial_baudrate": 19201,
"serial_error_correction": 5, "serial_error_correction": 5,
"use_serial_usb_adapter": true, "use_serial_usb_adapter": true,
"built_in_serial_interface": "ttyS0" "built_in_serial_interface": "ttyS0",
"rx_udp_ip": ""
}""") }""")
# Test # Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assertEqual(settings.serial_baudrate, 19200) self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 5) self.assertEqual(settings.serial_error_correction, 5)
self.assertEqual(settings.use_serial_usb_adapter, True) self.assertEqual(settings.use_serial_usb_adapter, True)
@ -395,10 +459,11 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": 19200, "serial_baudrate": 19200,
"serial_error_correction": -1, "serial_error_correction": -1,
"use_serial_usb_adapter": true, "use_serial_usb_adapter": true,
"built_in_serial_interface": "ttyS0" "built_in_serial_interface": "ttyS0",
"rx_udp_ip": ""
}""") }""")
# Test # Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assertEqual(settings.serial_baudrate, 19200) self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 5) self.assertEqual(settings.serial_error_correction, 5)
self.assertEqual(settings.use_serial_usb_adapter, True) self.assertEqual(settings.use_serial_usb_adapter, True)
@ -418,10 +483,11 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": 19200, "serial_baudrate": 19200,
"serial_error_correction": 5, "serial_error_correction": 5,
"use_serial_usb_adapter": true, "use_serial_usb_adapter": true,
"built_in_serial_interface": "does_not_exist" "built_in_serial_interface": "does_not_exist",
"rx_udp_ip": ""
}""") }""")
# Test # Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assertEqual(settings.serial_baudrate, 19200) self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 5) self.assertEqual(settings.serial_error_correction, 5)
self.assertEqual(settings.use_serial_usb_adapter, True) self.assertEqual(settings.use_serial_usb_adapter, True)
@ -432,6 +498,48 @@ class TestGatewaySettings(TFCTestCase):
self.assertEqual(data, self.default_serialized) self.assertEqual(data, self.default_serialized)
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
def test_invalid_rx_udp_ip_is_replaced_with_user_input(self, _) -> None:
# Setup
ensure_dir(DIR_USER_DATA)
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
f.write("""\
{
"serial_baudrate": 19200,
"serial_error_correction": 5,
"use_serial_usb_adapter": true,
"built_in_serial_interface": "ttyS0",
"rx_udp_ip": "256.256.256.256"
}""")
# Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=True)
self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 5)
self.assertEqual(settings.use_serial_usb_adapter, True)
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
self.assertEqual(settings.rx_udp_ip, '10.137.0.17')
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
def test_invalid_rx_udp_ip_type_is_replaced_with_user_input(self, _) -> None:
# Setup
ensure_dir(DIR_USER_DATA)
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
f.write("""\
{
"serial_baudrate": 19200,
"serial_error_correction": 5,
"use_serial_usb_adapter": true,
"built_in_serial_interface": "ttyS0",
"rx_udp_ip": 5
}""")
# Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=True)
self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 5)
self.assertEqual(settings.use_serial_usb_adapter, True)
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
self.assertEqual(settings.rx_udp_ip, '10.137.0.17')
def test_invalid_type_is_replaced_with_default(self) -> None: def test_invalid_type_is_replaced_with_default(self) -> None:
# Setup # Setup
ensure_dir(DIR_USER_DATA) ensure_dir(DIR_USER_DATA)
@ -441,10 +549,11 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": "115200", "serial_baudrate": "115200",
"serial_error_correction": "5", "serial_error_correction": "5",
"use_serial_usb_adapter": "true", "use_serial_usb_adapter": "true",
"built_in_serial_interface": true "built_in_serial_interface": true,
"rx_udp_ip": ""
}""") }""")
# Test # Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assertEqual(settings.serial_baudrate, 19200) self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 5) self.assertEqual(settings.serial_error_correction, 5)
self.assertEqual(settings.use_serial_usb_adapter, True) self.assertEqual(settings.use_serial_usb_adapter, True)
@ -468,7 +577,7 @@ class TestGatewaySettings(TFCTestCase):
"this_should_not_be_here": 1 "this_should_not_be_here": 1
}""") }""")
# Test # Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assertEqual(settings.serial_baudrate, 19200) self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 5) self.assertEqual(settings.serial_error_correction, 5)
self.assertEqual(settings.use_serial_usb_adapter, True) self.assertEqual(settings.use_serial_usb_adapter, True)
@ -487,7 +596,7 @@ class TestGatewaySettings(TFCTestCase):
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f: with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
f.write(self.default_serialized) f.write(self.default_serialized)
settings = GatewaySettings(operation=TX, local_test=False, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=False, dd_sockets=True, qubes=False)
# Test # Test
self.assertIsNone(settings.setup()) self.assertIsNone(settings.setup())
@ -495,7 +604,7 @@ class TestGatewaySettings(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_change_setting(self, _: Any) -> None: def test_change_setting(self, _: Any) -> None:
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assert_se("Error: Invalid setting value 'Falsee'.", self.assert_se("Error: Invalid setting value 'Falsee'.",
settings.change_setting, 'serial_baudrate', 'Falsee') settings.change_setting, 'serial_baudrate', 'Falsee')
self.assert_se("Error: Invalid setting value '1.1'.", self.assert_se("Error: Invalid setting value '1.1'.",
@ -506,14 +615,14 @@ class TestGatewaySettings(TFCTestCase):
settings.change_setting, 'use_serial_usb_adapter', 'Falsee') settings.change_setting, 'use_serial_usb_adapter', 'Falsee')
self.assertIsNone(settings.change_setting('serial_baudrate', '9600')) self.assertIsNone(settings.change_setting('serial_baudrate', '9600'))
self.assertEqual(GatewaySettings(operation=TX, local_test=True, dd_sockets=True).serial_baudrate, 9600) self.assertEqual(GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False).serial_baudrate, 9600)
settings.serial_baudrate = b'bytestring' settings.serial_baudrate = b'bytestring'
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
settings.change_setting('serial_baudrate', '9600') settings.change_setting('serial_baudrate', '9600')
def test_validate_key_value_pair(self) -> None: def test_validate_key_value_pair(self) -> None:
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assert_se("Error: The specified baud rate is not supported.", self.assert_se("Error: The specified baud rate is not supported.",
settings.validate_key_value_pair, 'serial_baudrate', 0) settings.validate_key_value_pair, 'serial_baudrate', 0)
self.assert_se("Error: The specified baud rate is not supported.", self.assert_se("Error: The specified baud rate is not supported.",
@ -529,11 +638,11 @@ class TestGatewaySettings(TFCTestCase):
@mock.patch('shutil.get_terminal_size', return_value=(64, 64)) @mock.patch('shutil.get_terminal_size', return_value=(64, 64))
def test_too_narrow_terminal_raises_fr_when_printing_settings(self, _: Any) -> None: def test_too_narrow_terminal_raises_fr_when_printing_settings(self, _: Any) -> None:
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assert_se("Error: Screen width is too small.", settings.print_settings) self.assert_se("Error: Screen width is too small.", settings.print_settings)
def test_print_settings(self) -> None: def test_print_settings(self) -> None:
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True) settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
self.assert_prints("""\ self.assert_prints("""\
Serial interface setting Current value Default value Description Serial interface setting Current value Default value Description

View File

@ -33,18 +33,18 @@ from typing import Any, NoReturn
from unittest.mock import MagicMock from unittest.mock import MagicMock
from src.common.misc import calculate_race_condition_delay, decompress, ensure_dir, get_tab_complete_list from src.common.misc import (calculate_race_condition_delay, decompress, ensure_dir, get_tab_complete_list,
from src.common.misc import get_tab_completer, get_terminal_height, get_terminal_width, HideRunTime, ignored get_tab_completer, get_terminal_height, get_terminal_width, HideRunTime, ignored,
from src.common.misc import monitor_processes, process_arguments, readable_size, reset_terminal, round_up monitor_processes, process_arguments, readable_size, reset_terminal, round_up,
from src.common.misc import separate_header, separate_headers, separate_trailer, split_string, split_byte_string separate_header, separate_headers, separate_trailer, split_string, split_byte_string,
from src.common.misc import split_to_substrings, terminal_width_check, validate_group_name, validate_key_exchange split_to_substrings, terminal_width_check, validate_group_name, validate_ip_address,
from src.common.misc import validate_onion_addr, validate_nick validate_key_exchange, validate_onion_addr, validate_nick)
from src.common.statics import (DIR_RECV_FILES, DIR_USER_DATA, DUMMY_GROUP, ECDHE, EXIT, EXIT_QUEUE, LOCAL_ID, from src.common.statics import (DIR_RECV_FILES, DIR_USER_DATA, DUMMY_GROUP, ECDHE, EXIT, EXIT_QUEUE, LOCAL_ID,
PADDING_LENGTH, RESET, RX, TAILS, TRAFFIC_MASKING, WIPE) PADDING_LENGTH, RESET, RX, TAILS, TRAFFIC_MASKING, WIPE)
from tests.mock_classes import ContactList, Gateway, GroupList, Settings from tests.mock_classes import ContactList, Gateway, GroupList, Settings
from tests.utils import cd_unit_test, cleanup, gen_queue_dict, nick_to_onion_address from tests.utils import (cd_unit_test, cleanup, gen_queue_dict, nick_to_onion_address, nick_to_pub_key,
from tests.utils import nick_to_pub_key, tear_queues, TFCTestCase tear_queues, TFCTestCase)
class TestCalculateRaceConditionDelay(unittest.TestCase): class TestCalculateRaceConditionDelay(unittest.TestCase):
@ -72,7 +72,7 @@ class TestDecompress(TFCTestCase):
# Test # Test
self.assertEqual(decompress(compressed, self.settings.max_decompress_size), data) self.assertEqual(decompress(compressed, self.settings.max_decompress_size), data)
def test_oversize_decompression_raises_se(self) -> None: def test_oversize_decompression_raises_soft_error(self) -> None:
# Setup # Setup
data = os.urandom(self.settings.max_decompress_size + 1) data = os.urandom(self.settings.max_decompress_size + 1)
compressed = zlib.compress(data) compressed = zlib.compress(data)
@ -272,9 +272,10 @@ class TestProcessArguments(unittest.TestCase):
def __init__(self) -> None: def __init__(self) -> None:
"""Create new Args mock object.""" """Create new Args mock object."""
self.operation = True self.operation = True
self.local_test = True self.local_test = True
self.data_diode_sockets = True self.data_diode_sockets = True
self.qubes = False
class MockParser(object): class MockParser(object):
"""MockParse object.""" """MockParse object."""
@ -298,7 +299,7 @@ class TestProcessArguments(unittest.TestCase):
argparse.ArgumentParser = self.o_argparse argparse.ArgumentParser = self.o_argparse
def test_process_arguments(self) -> None: def test_process_arguments(self) -> None:
self.assertEqual(process_arguments(), (RX, True, True)) self.assertEqual(process_arguments(), (RX, True, True, False))
class TestReadableSize(unittest.TestCase): class TestReadableSize(unittest.TestCase):
@ -499,6 +500,16 @@ class TestValidateGroupName(unittest.TestCase):
'') '')
class TestValidateIpAddress(unittest.TestCase):
def test_validate_ip_address(self) -> None:
self.assertEqual(validate_ip_address("10.137.0.17"), '')
self.assertEqual(validate_ip_address("10.137.0.255"), '')
self.assertEqual(validate_ip_address("255.255.255.255"), '')
self.assertEqual(validate_ip_address("10.137.0.256"), 'Invalid IP address')
self.assertEqual(validate_ip_address("256.256.256.256"), 'Invalid IP address')
class TestValidateKeyExchange(unittest.TestCase): class TestValidateKeyExchange(unittest.TestCase):
def test_validate_key_exchange(self) -> None: def test_validate_key_exchange(self) -> None:

View File

@ -25,8 +25,8 @@ from datetime import datetime
from unittest import mock from unittest import mock
from typing import Any from typing import Any
from src.common.output import clear_screen, group_management_print, m_print, phase, print_fingerprint, print_key from src.common.output import (clear_screen, group_management_print, m_print, phase, print_fingerprint, print_key,
from src.common.output import print_title, print_on_previous_line, print_spacing, rp_print print_title, print_on_previous_line, print_spacing, rp_print)
from src.common.statics import (ADDED_MEMBERS, ALREADY_MEMBER, BOLD_ON, CLEAR_ENTIRE_LINE, CLEAR_ENTIRE_SCREEN, from src.common.statics import (ADDED_MEMBERS, ALREADY_MEMBER, BOLD_ON, CLEAR_ENTIRE_LINE, CLEAR_ENTIRE_SCREEN,
CURSOR_LEFT_UP_CORNER, CURSOR_UP_ONE_LINE, DONE, FINGERPRINT_LENGTH, NEW_GROUP, CURSOR_LEFT_UP_CORNER, CURSOR_UP_ONE_LINE, DONE, FINGERPRINT_LENGTH, NEW_GROUP,
NORMAL_TEXT, NOT_IN_GROUP, REMOVED_MEMBERS, RX, SYMMETRIC_KEY_LENGTH, TX, NORMAL_TEXT, NOT_IN_GROUP, REMOVED_MEMBERS, RX, SYMMETRIC_KEY_LENGTH, TX,

View File

@ -63,7 +63,7 @@ class TestAskPathGui(TFCTestCase):
@mock.patch('tkinter.Tk', return_value=MagicMock()) @mock.patch('tkinter.Tk', return_value=MagicMock())
@mock.patch('tkinter.filedialog.askopenfilename', return_value='') @mock.patch('tkinter.filedialog.askopenfilename', return_value='')
def test_no_path_to_file_raises_se(self, *_: Any) -> None: def test_no_path_to_file_raises_soft_error(self, *_: Any) -> None:
self.assert_se("File selection aborted.", ask_path_gui, 'test message', self.settings, True) self.assert_se("File selection aborted.", ask_path_gui, 'test message', self.settings, True)
@mock.patch('tkinter.Tk', return_value=MagicMock()) @mock.patch('tkinter.Tk', return_value=MagicMock())
@ -73,7 +73,7 @@ class TestAskPathGui(TFCTestCase):
@mock.patch('tkinter.Tk', return_value=MagicMock()) @mock.patch('tkinter.Tk', return_value=MagicMock())
@mock.patch('tkinter.filedialog.askdirectory', return_value='') @mock.patch('tkinter.filedialog.askdirectory', return_value='')
def test_no_path_raises_se(self, *_: Any) -> None: def test_no_path_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Path selection aborted.", ask_path_gui, 'test message', self.settings, False) self.assert_se("Path selection aborted.", ask_path_gui, 'test message', self.settings, False)

View File

@ -75,66 +75,66 @@ class TestReedSolomon(unittest.TestCase):
self.assertEqual(dec_enc2, enc) self.assertEqual(dec_enc2, enc)
def test_prim_fcr_basic(self) -> None: def test_prim_fcr_basic(self) -> None:
nn = 30 nn = 30
kk = 18 kk = 18
tt = nn - kk tt = nn - kk
rs = RSCodec(tt, fcr=120, prim=0x187) rs = RSCodec(tt, fcr=120, prim=0x187)
hexencmsg = ('00faa123555555c000000354064432' hex_enc_msg = ('00faa123555555c000000354064432'
'c02800fe97c434e1ff5365cf8fafe4') 'c02800fe97c434e1ff5365cf8fafe4')
strf = str strf = str
encmsg = bytearray.fromhex(strf(hexencmsg)) enc_msg = bytearray.fromhex(strf(hex_enc_msg))
decmsg = encmsg[:kk] dec_msg = enc_msg[:kk]
tem = rs.encode(decmsg) tem = rs.encode(dec_msg)
self.assertEqual(encmsg, tem, msg="encoded does not match expected") self.assertEqual(enc_msg, tem, msg="encoded does not match expected")
tdm, rtem = rs.decode(tem) tdm, rtem = rs.decode(tem)
self.assertEqual(tdm, decmsg, msg="decoded does not match original") self.assertEqual(tdm, dec_msg, msg="decoded does not match original")
self.assertEqual(rtem, tem, msg="decoded mesecc does not match original") self.assertEqual(rtem, tem, msg="decoded mesecc does not match original")
tem1 = bytearray(tem) # Clone a copy tem1 = bytearray(tem) # Clone a copy
# Encoding and decoding intact message seem OK, so test errors # Encoding and decoding intact message seem OK, so test errors
numerrs = tt >> 1 # Inject tt/2 errors (expected to recover fully) num_errs = tt >> 1 # Inject tt/2 errors (expected to recover fully)
for i in sample(range(nn), numerrs): # inject errors in random places for i in sample(range(nn), num_errs): # inject errors in random places
tem1[i] ^= 0xff # flip all 8 bits tem1[i] ^= 0xff # flip all 8 bits
tdm, _ = rs.decode(tem1) tdm, _ = rs.decode(tem1)
self.assertEqual(tdm, decmsg, msg="decoded with errors does not match original") self.assertEqual(tdm, dec_msg, msg="decoded with errors does not match original")
tem1 = bytearray(tem) # Clone another copy tem1 = bytearray(tem) # Clone another copy
numerrs += 1 # Inject tt/2 + 1 errors (expected to fail and detect it) num_errs += 1 # Inject tt/2 + 1 errors (expected to fail and detect it)
for i in sample(range(nn), numerrs): # Inject errors in random places for i in sample(range(nn), num_errs): # Inject errors in random places
tem1[i] ^= 0xff # Flip all 8 bits tem1[i] ^= 0xff # Flip all 8 bits
# If this fails, it means excessive errors not detected # If this fails, it means excessive errors not detected
self.assertRaises(ReedSolomonError, rs.decode, tem1) self.assertRaises(ReedSolomonError, rs.decode, tem1)
def test_prim_fcr_long(self) -> None: def test_prim_fcr_long(self) -> None:
nn = 48 nn = 48
kk = 34 kk = 34
tt = nn - kk tt = nn - kk
rs = RSCodec(tt, fcr=120, prim=0x187) rs = RSCodec(tt, fcr=120, prim=0x187)
hexencmsg = ('08faa123555555c000000354064432c0280e1b4d090cfc04' hex_enc_msg = ('08faa123555555c000000354064432c0280e1b4d090cfc04'
'887400000003500000000e1985ff9c6b33066ca9f43d12e8') '887400000003500000000e1985ff9c6b33066ca9f43d12e8')
strf = str strf = str
encmsg = bytearray.fromhex(strf(hexencmsg)) enc_msg = bytearray.fromhex(strf(hex_enc_msg))
decmsg = encmsg[:kk] dec_msg = enc_msg[:kk]
tem = rs.encode(decmsg) tem = rs.encode(dec_msg)
self.assertEqual(encmsg, tem, msg="encoded does not match expected") self.assertEqual(enc_msg, tem, msg="encoded does not match expected")
tdm, rtem = rs.decode(tem) tdm, rtem = rs.decode(tem)
self.assertEqual(tdm, decmsg, msg="decoded does not match original") self.assertEqual(tdm, dec_msg, msg="decoded does not match original")
self.assertEqual(rtem, tem, msg="decoded mesecc does not match original") self.assertEqual(rtem, tem, msg="decoded mesecc does not match original")
tem1 = bytearray(tem) tem1 = bytearray(tem)
numerrs = tt >> 1 num_errs = tt >> 1
for i in sample(range(nn), numerrs): for i in sample(range(nn), num_errs):
tem1[i] ^= 0xff tem1[i] ^= 0xff
tdm, rtem = rs.decode(tem1) tdm, rtem = rs.decode(tem1)
self.assertEqual(tdm, decmsg, msg="decoded with errors does not match original") self.assertEqual(tdm, dec_msg, msg="decoded with errors does not match original")
self.assertEqual(rtem, tem, msg="decoded mesecc with errors does not match original") self.assertEqual(rtem, tem, msg="decoded mesecc with errors does not match original")
tem1 = bytearray(tem) tem1 = bytearray(tem)
numerrs += 1 num_errs += 1
for i in sample(range(nn), numerrs): for i in sample(range(nn), num_errs):
tem1[i] ^= 0xff tem1[i] ^= 0xff
self.assertRaises(ReedSolomonError, rs.decode, tem1) self.assertRaises(ReedSolomonError, rs.decode, tem1)

View File

@ -265,6 +265,7 @@ class Settings(OrigSettings):
self.master_key = MasterKey() self.master_key = MasterKey()
self.software_operation = TX self.software_operation = TX
self.local_testing_mode = False self.local_testing_mode = False
self.qubes = False
self.all_keys = list(vars(self).keys()) self.all_keys = list(vars(self).keys())
self.key_list = self.all_keys[:self.all_keys.index('master_key')] self.key_list = self.all_keys[:self.all_keys.index('master_key')]
@ -304,6 +305,8 @@ class GatewaySettings(OrigGatewaySettings):
self.local_testing_mode = False self.local_testing_mode = False
self.data_diode_sockets = False self.data_diode_sockets = False
self.qubes = False
self.all_keys = list(vars(self).keys()) self.all_keys = list(vars(self).keys())
self.key_list = self.all_keys[:self.all_keys.index('software_operation')] self.key_list = self.all_keys[:self.all_keys.index('software_operation')]
self.defaults = {k: self.__dict__[k] for k in self.key_list} self.defaults = {k: self.__dict__[k] for k in self.key_list}

View File

@ -36,14 +36,14 @@ from src.common.statics import (CH_FILE_RECV, CH_LOGGING, CH_NOTIFY, CLEAR_ENTI
LOCAL_PUBKEY, MESSAGE, ORIGIN_CONTACT_HEADER, PADDING_LENGTH, RESET, RX, LOCAL_PUBKEY, MESSAGE, ORIGIN_CONTACT_HEADER, PADDING_LENGTH, RESET, RX,
SYMMETRIC_KEY_LENGTH, US_BYTE, WIN_TYPE_CONTACT, WIN_TYPE_GROUP, WIN_UID_FILE, WIPE) SYMMETRIC_KEY_LENGTH, US_BYTE, WIN_TYPE_CONTACT, WIN_TYPE_GROUP, WIN_UID_FILE, WIPE)
from src.receiver.commands import (ch_contact_s, ch_master_key, ch_nick, ch_setting, contact_rem, exit_tfc, log_command,
process_command, remove_log, reset_screen, win_activity, win_select, wipe)
from src.receiver.packet import PacketList from src.receiver.packet import PacketList
from src.receiver.commands import ch_contact_s, ch_master_key, ch_nick, ch_setting, contact_rem, exit_tfc, log_command
from src.receiver.commands import process_command, remove_log, reset_screen, win_activity, win_select, wipe
from tests.mock_classes import ContactList, Gateway, group_name_to_group_id, GroupList, KeyList, MasterKey from tests.mock_classes import (ContactList, Gateway, group_name_to_group_id, GroupList, KeyList, MasterKey,
from tests.mock_classes import nick_to_pub_key, RxWindow, Settings, WindowList nick_to_pub_key, RxWindow, Settings, WindowList)
from tests.utils import assembly_packet_creator, cd_unit_test, cleanup, ignored, nick_to_short_address from tests.utils import (assembly_packet_creator, cd_unit_test, cleanup, ignored, nick_to_short_address,
from tests.utils import tear_queue, TFCTestCase tear_queue, TFCTestCase)
class TestProcessCommand(TFCTestCase): class TestProcessCommand(TFCTestCase):
@ -71,7 +71,7 @@ class TestProcessCommand(TFCTestCase):
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
tear_queue(self.exit_queue) tear_queue(self.exit_queue)
def test_incomplete_command_raises_se(self) -> None: def test_incomplete_command_raises_soft_error(self) -> None:
packet = assembly_packet_creator(COMMAND, b'test_command', s_header_override=C_L_HEADER, encrypt_packet=True)[0] packet = assembly_packet_creator(COMMAND, b'test_command', s_header_override=C_L_HEADER, encrypt_packet=True)[0]
self.assert_se("Incomplete command.", process_command, self.ts, packet, *self.args) self.assert_se("Incomplete command.", process_command, self.ts, packet, *self.args)
@ -316,7 +316,7 @@ class TestChMasterKey(TFCTestCase):
@mock.patch('getpass.getpass', return_value='a') @mock.patch('getpass.getpass', return_value='a')
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('os.getrandom', side_effect=KeyboardInterrupt) @mock.patch('os.getrandom', side_effect=KeyboardInterrupt)
def test_keyboard_interrupt_raises_se(self, *_) -> None: def test_keyboard_interrupt_raises_soft_error(self, *_) -> None:
self.assert_se("Error: Invalid password.", ch_master_key, *self.args) self.assert_se("Error: Invalid password.", ch_master_key, *self.args)
@ -332,7 +332,7 @@ class TestChNick(TFCTestCase):
self.window = self.window_list.get_window(nick_to_pub_key("Alice")) self.window = self.window_list.get_window(nick_to_pub_key("Alice"))
self.window.type = WIN_TYPE_CONTACT self.window.type = WIN_TYPE_CONTACT
def test_unknown_account_raises_se(self) -> None: def test_unknown_account_raises_soft_error(self) -> None:
# Setup # Setup
cmd_data = nick_to_pub_key("Bob") + b'Bob_' cmd_data = nick_to_pub_key("Bob") + b'Bob_'
@ -364,7 +364,7 @@ class TestChSetting(TFCTestCase):
self.args = (self.ts, self.window_list, self.contact_list, self.group_list, self.args = (self.ts, self.window_list, self.contact_list, self.group_list,
self.key_list, self.settings, self.gateway) self.key_list, self.settings, self.gateway)
def test_invalid_data_raises_se(self) -> None: def test_invalid_data_raises_soft_error(self) -> None:
# Setup # Setup
self.settings.key_list = [''] self.settings.key_list = ['']
@ -372,7 +372,7 @@ class TestChSetting(TFCTestCase):
cmd_data = b'setting' + b'True' cmd_data = b'setting' + b'True'
self.assert_se("Error: Received invalid setting data.", ch_setting, cmd_data, *self.args) self.assert_se("Error: Received invalid setting data.", ch_setting, cmd_data, *self.args)
def test_invalid_setting_raises_se(self) -> None: def test_invalid_setting_raises_soft_error(self) -> None:
# Setup # Setup
self.settings.key_list = [''] self.settings.key_list = ['']
@ -411,7 +411,7 @@ class TestChContactSetting(TFCTestCase):
group_list=self.group_list) group_list=self.group_list)
self.args = self.ts, self.window_list, self.contact_list, self.group_list self.args = self.ts, self.window_list, self.contact_list, self.group_list
def test_invalid_window_raises_se(self) -> None: def test_invalid_window_raises_soft_error(self) -> None:
# Setup # Setup
cmd_data = ENABLE + nick_to_pub_key("Bob") cmd_data = ENABLE + nick_to_pub_key("Bob")
header = CH_LOGGING header = CH_LOGGING
@ -503,7 +503,7 @@ class TestContactRemove(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_no_contact_raises_se(self) -> None: def test_no_contact_raises_soft_error(self) -> None:
# Setup # Setup
contact_list = ContactList(nicks=['Alice']) contact_list = ContactList(nicks=['Alice'])
group_list = GroupList(groups=[]) group_list = GroupList(groups=[])

View File

@ -39,7 +39,7 @@ class TestGroupCreate(TFCTestCase):
self.window_list = WindowList() self.window_list = WindowList()
self.group_id = group_name_to_group_id('test_group') self.group_id = group_name_to_group_id('test_group')
def test_too_many_purp_accounts_raises_se(self) -> None: def test_too_many_purp_accounts_raises_soft_error(self) -> None:
# Setup # Setup
create_list = [nick_to_pub_key(str(n)) for n in range(51)] create_list = [nick_to_pub_key(str(n)) for n in range(51)]
cmd_data = self.group_id + b'test_group' + US_BYTE + b''.join(create_list) cmd_data = self.group_id + b'test_group' + US_BYTE + b''.join(create_list)
@ -52,7 +52,7 @@ class TestGroupCreate(TFCTestCase):
self.assert_se("Error: TFC settings only allow 50 members per group.", self.assert_se("Error: TFC settings only allow 50 members per group.",
group_create, cmd_data, self.ts, self.window_list, contact_list, group_list, self.settings) group_create, cmd_data, self.ts, self.window_list, contact_list, group_list, self.settings)
def test_full_group_list_raises_se(self) -> None: def test_full_group_list_raises_soft_error(self) -> None:
# Setup # Setup
cmd_data = self.group_id + b'test_group' + US_BYTE + nick_to_pub_key('51') cmd_data = self.group_id + b'test_group' + US_BYTE + nick_to_pub_key('51')
group_list = GroupList(groups=[f"test_group_{n}" for n in range(50)]) group_list = GroupList(groups=[f"test_group_{n}" for n in range(50)])
@ -85,7 +85,7 @@ class TestGroupAdd(TFCTestCase):
self.settings = Settings() self.settings = Settings()
self.window_list = WindowList() self.window_list = WindowList()
def test_too_large_final_member_list_raises_se(self) -> None: def test_too_large_final_member_list_raises_soft_error(self) -> None:
# Setup # Setup
group_list = GroupList(groups=['test_group']) group_list = GroupList(groups=['test_group'])
contact_list = ContactList(nicks=[str(n) for n in range(51)]) contact_list = ContactList(nicks=[str(n) for n in range(51)])
@ -97,7 +97,7 @@ class TestGroupAdd(TFCTestCase):
self.assert_se("Error: TFC settings only allow 50 members per group.", self.assert_se("Error: TFC settings only allow 50 members per group.",
group_add, cmd_data, self.ts, self.window_list, contact_list, group_list, self.settings) group_add, cmd_data, self.ts, self.window_list, contact_list, group_list, self.settings)
def test_unknown_group_id_raises_se(self) -> None: def test_unknown_group_id_raises_soft_error(self) -> None:
# Setup # Setup
group_list = GroupList(groups=['test_group']) group_list = GroupList(groups=['test_group'])
contact_list = ContactList(nicks=[str(n) for n in range(21)]) contact_list = ContactList(nicks=[str(n) for n in range(21)])
@ -137,7 +137,7 @@ class TestGroupRemove(TFCTestCase):
self.group.members = self.contact_list.contacts[:19] self.group.members = self.contact_list.contacts[:19]
self.settings = Settings() self.settings = Settings()
def test_unknown_group_id_raises_se(self) -> None: def test_unknown_group_id_raises_soft_error(self) -> None:
# Setup # Setup
group_list = GroupList(groups=['test_group']) group_list = GroupList(groups=['test_group'])
contact_list = ContactList(nicks=[str(n) for n in range(21)]) contact_list = ContactList(nicks=[str(n) for n in range(21)])
@ -161,12 +161,12 @@ class TestGroupDelete(TFCTestCase):
self.window_list = WindowList() self.window_list = WindowList()
self.group_list = GroupList(groups=['test_group']) self.group_list = GroupList(groups=['test_group'])
def test_missing_group_raises_se(self) -> None: def test_missing_group_raises_soft_error(self) -> None:
cmd_data = group_name_to_group_id('test_group2') cmd_data = group_name_to_group_id('test_group2')
self.assert_se("Error: No group with ID '2e7mHQznTMsP6' found.", self.assert_se("Error: No group with ID '2e7mHQznTMsP6' found.",
group_delete, cmd_data, self.ts, self.window_list, self.group_list) group_delete, cmd_data, self.ts, self.window_list, self.group_list)
def test_unknown_group_id_raises_se(self) -> None: def test_unknown_group_id_raises_soft_error(self) -> None:
# Setup # Setup
group_list = GroupList(groups=['test_group']) group_list = GroupList(groups=['test_group'])
cmd_data = group_name_to_group_id('test_group2') cmd_data = group_name_to_group_id('test_group2')
@ -193,21 +193,21 @@ class TestGroupRename(TFCTestCase):
self.contact_list = ContactList(nicks=['alice']) self.contact_list = ContactList(nicks=['alice'])
self.args = self.ts, self.window_list, self.contact_list, self.group_list self.args = self.ts, self.window_list, self.contact_list, self.group_list
def test_missing_group_id_raises_se(self) -> None: def test_missing_group_id_raises_soft_error(self) -> None:
# Setup # Setup
cmd_data = group_name_to_group_id('test_group2') + b'new_name' cmd_data = group_name_to_group_id('test_group2') + b'new_name'
# Test # Test
self.assert_se("Error: No group with ID '2e7mHQznTMsP6' found.", group_rename, cmd_data, *self.args) self.assert_se("Error: No group with ID '2e7mHQznTMsP6' found.", group_rename, cmd_data, *self.args)
def test_invalid_group_name_encoding_raises_se(self) -> None: def test_invalid_group_name_encoding_raises_soft_error(self) -> None:
# Setup # Setup
cmd_data = group_name_to_group_id('test_group') + b'new_name' + UNDECODABLE_UNICODE cmd_data = group_name_to_group_id('test_group') + b'new_name' + UNDECODABLE_UNICODE
# Test # Test
self.assert_se("Error: New name for group 'test_group' was invalid.", group_rename, cmd_data, *self.args) self.assert_se("Error: New name for group 'test_group' was invalid.", group_rename, cmd_data, *self.args)
def test_invalid_group_name_raises_se(self) -> None: def test_invalid_group_name_raises_soft_error(self) -> None:
# Setup # Setup
cmd_data = group_name_to_group_id('test_group') + b'new_name\x1f' cmd_data = group_name_to_group_id('test_group') + b'new_name\x1f'

View File

@ -73,7 +73,7 @@ class ProcessAssembledFile(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_invalid_structure_raises_se(self) -> None: def test_invalid_structure_raises_soft_error(self) -> None:
# Setup # Setup
payload = b'testfile.txt' payload = b'testfile.txt'
@ -81,7 +81,7 @@ class ProcessAssembledFile(TFCTestCase):
self.assert_se("Error: Received file had an invalid structure.", self.assert_se("Error: Received file had an invalid structure.",
process_assembled_file, self.ts, payload, *self.args) process_assembled_file, self.ts, payload, *self.args)
def test_invalid_encoding_raises_se(self) -> None: def test_invalid_encoding_raises_soft_error(self) -> None:
# Setup # Setup
payload = UNDECODABLE_UNICODE + US_BYTE + b'file_data' payload = UNDECODABLE_UNICODE + US_BYTE + b'file_data'
@ -89,7 +89,7 @@ class ProcessAssembledFile(TFCTestCase):
self.assert_se("Error: Received file name had an invalid encoding.", self.assert_se("Error: Received file name had an invalid encoding.",
process_assembled_file, self.ts, payload, *self.args) process_assembled_file, self.ts, payload, *self.args)
def test_invalid_name_raises_se(self) -> None: def test_invalid_name_raises_soft_error(self) -> None:
# Setup # Setup
payload = b'\x01filename' + US_BYTE + b'file_data' payload = b'\x01filename' + US_BYTE + b'file_data'
@ -97,7 +97,7 @@ class ProcessAssembledFile(TFCTestCase):
self.assert_se("Error: Received file had an invalid name.", self.assert_se("Error: Received file had an invalid name.",
process_assembled_file, self.ts, payload, *self.args) process_assembled_file, self.ts, payload, *self.args)
def test_slash_in_file_name_raises_se(self) -> None: def test_slash_in_file_name_raises_soft_error(self) -> None:
# Setup # Setup
payload = b'file/name' + US_BYTE + b'file_data' payload = b'file/name' + US_BYTE + b'file_data'
@ -105,7 +105,7 @@ class ProcessAssembledFile(TFCTestCase):
self.assert_se("Error: Received file had an invalid name.", self.assert_se("Error: Received file had an invalid name.",
process_assembled_file, self.ts, payload, *self.args) process_assembled_file, self.ts, payload, *self.args)
def test_invalid_key_raises_se(self) -> None: def test_invalid_key_raises_soft_error(self) -> None:
# Setup # Setup
payload = b'testfile.txt' + US_BYTE + b'file_data' payload = b'testfile.txt' + US_BYTE + b'file_data'
@ -113,7 +113,7 @@ class ProcessAssembledFile(TFCTestCase):
self.assert_se("Error: Received file had an invalid key.", self.assert_se("Error: Received file had an invalid key.",
process_assembled_file, self.ts, payload, *self.args) process_assembled_file, self.ts, payload, *self.args)
def test_decryption_fail_raises_se(self) -> None: def test_decryption_fail_raises_soft_error(self) -> None:
# Setup # Setup
file_data = encrypt_and_sign(b'file_data', self.key)[::-1] file_data = encrypt_and_sign(b'file_data', self.key)[::-1]
payload = b'testfile.txt' + US_BYTE + file_data payload = b'testfile.txt' + US_BYTE + file_data
@ -122,7 +122,7 @@ class ProcessAssembledFile(TFCTestCase):
self.assert_se("Error: Decryption of file data failed.", self.assert_se("Error: Decryption of file data failed.",
process_assembled_file, self.ts, payload, *self.args) process_assembled_file, self.ts, payload, *self.args)
def test_invalid_compression_raises_se(self) -> None: def test_invalid_compression_raises_soft_error(self) -> None:
# Setup # Setup
compressed = zlib.compress(b'file_data', level=COMPRESSION_LEVEL)[::-1] compressed = zlib.compress(b'file_data', level=COMPRESSION_LEVEL)[::-1]
file_data = encrypt_and_sign(compressed, self.key) + self.key file_data = encrypt_and_sign(compressed, self.key) + self.key
@ -178,7 +178,7 @@ class TestNewFile(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_unknown_account_raises_se(self) -> None: def test_unknown_account_raises_soft_error(self) -> None:
# Setup # Setup
file_ct = encrypt_and_sign(self.compressed, self.file_key) file_ct = encrypt_and_sign(self.compressed, self.file_key)
packet = nick_to_pub_key('Bob') + ORIGIN_CONTACT_HEADER + file_ct packet = nick_to_pub_key('Bob') + ORIGIN_CONTACT_HEADER + file_ct
@ -186,7 +186,7 @@ class TestNewFile(TFCTestCase):
# Test # Test
self.assert_se("File from an unknown account.", new_file, self.ts, packet, *self.args) self.assert_se("File from an unknown account.", new_file, self.ts, packet, *self.args)
def test_disabled_file_reception_raises_se(self) -> None: def test_disabled_file_reception_raises_soft_error(self) -> None:
# Setup # Setup
file_ct = encrypt_and_sign(self.compressed, self.file_key) file_ct = encrypt_and_sign(self.compressed, self.file_key)
packet = nick_to_pub_key('Alice') + ORIGIN_CONTACT_HEADER + file_ct packet = nick_to_pub_key('Alice') + ORIGIN_CONTACT_HEADER + file_ct
@ -237,13 +237,14 @@ class TestProcessFile(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_invalid_key_raises_se(self) -> None: def test_invalid_key_raises_soft_error(self) -> None:
self.file_key = SYMMETRIC_KEY_LENGTH * b'f' self.file_key = SYMMETRIC_KEY_LENGTH * b'f'
self.args = self.file_key, self.contact_list, self.window_list, self.settings self.args = self.file_key, self.contact_list, self.window_list, self.settings
self.assert_se("Error: Decryption key for file from Alice was invalid.", self.assert_se("Error: Decryption key for file from Alice was invalid.",
process_file, self.ts, self.account, self.file_ct, *self.args) process_file, self.ts, self.account, self.file_ct, *self.args)
def test_invalid_compression_raises_se(self) -> None: def test_invalid_compression_raises_soft_error(self) -> None:
compressed = zlib.compress(b'file_data', level=COMPRESSION_LEVEL)[::-1] compressed = zlib.compress(b'file_data', level=COMPRESSION_LEVEL)[::-1]
file_data = encrypt_and_sign(compressed, self.file_key) file_data = encrypt_and_sign(compressed, self.file_key)
@ -251,7 +252,7 @@ class TestProcessFile(TFCTestCase):
process_file, self.ts, self.account, file_data, *self.args) process_file, self.ts, self.account, file_data, *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_invalid_file_name_raises_se(self, _: Any) -> None: def test_invalid_file_name_raises_soft_error(self, _: Any) -> None:
compressed = zlib.compress(UNDECODABLE_UNICODE + b'file_data', level=COMPRESSION_LEVEL) compressed = zlib.compress(UNDECODABLE_UNICODE + b'file_data', level=COMPRESSION_LEVEL)
file_data = encrypt_and_sign(compressed, self.file_key) file_data = encrypt_and_sign(compressed, self.file_key)
@ -259,7 +260,7 @@ class TestProcessFile(TFCTestCase):
process_file, self.ts, self.account, file_data, *self.args) process_file, self.ts, self.account, file_data, *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_non_printable_name_raises_se(self, _: Any) -> None: def test_non_printable_name_raises_soft_error(self, _: Any) -> None:
compressed = zlib.compress(str_to_bytes("file\x01") + b'file_data', level=COMPRESSION_LEVEL) compressed = zlib.compress(str_to_bytes("file\x01") + b'file_data', level=COMPRESSION_LEVEL)
file_data = encrypt_and_sign(compressed, self.file_key) file_data = encrypt_and_sign(compressed, self.file_key)
@ -267,7 +268,7 @@ class TestProcessFile(TFCTestCase):
process_file, self.ts, self.account, file_data, *self.args) process_file, self.ts, self.account, file_data, *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_slash_in_name_raises_se(self, _: Any) -> None: def test_slash_in_name_raises_soft_error(self, _: Any) -> None:
compressed = zlib.compress(str_to_bytes("Alice/file.txt") + b'file_data', level=COMPRESSION_LEVEL) compressed = zlib.compress(str_to_bytes("Alice/file.txt") + b'file_data', level=COMPRESSION_LEVEL)
file_data = encrypt_and_sign(compressed, self.file_key) file_data = encrypt_and_sign(compressed, self.file_key)

View File

@ -40,8 +40,8 @@ from src.common.statics import (ARGON2_SALT_LENGTH, BOLD_ON, CLEAR_ENTIRE_SCR
from src.receiver.key_exchanges import key_ex_ecdhe, key_ex_psk_rx, key_ex_psk_tx, local_key_rdy, process_local_key from src.receiver.key_exchanges import key_ex_ecdhe, key_ex_psk_rx, key_ex_psk_tx, local_key_rdy, process_local_key
from tests.mock_classes import Contact, ContactList, KeyList, KeySet, Settings, WindowList from tests.mock_classes import Contact, ContactList, KeyList, KeySet, Settings, WindowList
from tests.utils import cd_unit_test, cleanup, nick_to_short_address, nick_to_pub_key, tear_queue, TFCTestCase from tests.utils import (cd_unit_test, cleanup, nick_to_short_address, nick_to_pub_key, tear_queue, TFCTestCase,
from tests.utils import UNDECODABLE_UNICODE UNDECODABLE_UNICODE)
class TestProcessLocalKey(TFCTestCase): class TestProcessLocalKey(TFCTestCase):
@ -73,7 +73,7 @@ class TestProcessLocalKey(TFCTestCase):
@mock.patch('tkinter.Tk', return_value=MagicMock()) @mock.patch('tkinter.Tk', return_value=MagicMock())
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', return_value='5KfgdgUvseWfNkoUPWSvxMPNStu5wBBxyjz1zpZtLEjk7ZvwEAT') @mock.patch('builtins.input', return_value='5KfgdgUvseWfNkoUPWSvxMPNStu5wBBxyjz1zpZtLEjk7ZvwEAT')
def test_invalid_decryption_key_raises_se(self, *_: Any) -> None: def test_invalid_decryption_key_raises_soft_error(self, *_: Any) -> None:
# Setup # Setup
packet = b'' packet = b''
self.key_list.keysets = [] self.key_list.keysets = []
@ -104,7 +104,7 @@ class TestProcessLocalKey(TFCTestCase):
@mock.patch('tkinter.Tk', return_value=MagicMock()) @mock.patch('tkinter.Tk', return_value=MagicMock())
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=KeyboardInterrupt) @mock.patch('builtins.input', side_effect=KeyboardInterrupt)
def test_keyboard_interrupt_raises_se(self, *_: Any) -> None: def test_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None:
# Setup # Setup
self.window_list.active_win = self.window_list.get_window(nick_to_pub_key('Alice')) self.window_list.active_win = self.window_list.get_window(nick_to_pub_key('Alice'))
@ -115,7 +115,7 @@ class TestProcessLocalKey(TFCTestCase):
@mock.patch('tkinter.Tk', return_value=MagicMock()) @mock.patch('tkinter.Tk', return_value=MagicMock())
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=[b58encode(kek), b58encode(kek), b58encode(kek), b58encode(new_kek)]) @mock.patch('builtins.input', side_effect=[b58encode(kek), b58encode(kek), b58encode(kek), b58encode(new_kek)])
def test_old_local_key_packet_raises_se(self, *_: Any) -> None: def test_old_local_key_packet_raises_soft_error(self, *_: Any) -> None:
# Setup # Setup
self.key_list.keysets = [] self.key_list.keysets = []
new_key = os.urandom(SYMMETRIC_KEY_LENGTH) new_key = os.urandom(SYMMETRIC_KEY_LENGTH)
@ -202,7 +202,7 @@ class TestKeyExECDHE(TFCTestCase):
self.args = self.packet, self.ts, self.window_list, self.contact_list, self.key_list, self.settings self.args = self.packet, self.ts, self.window_list, self.contact_list, self.key_list, self.settings
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_invalid_nick_raises_se(self, _: Any) -> None: def test_invalid_nick_raises_soft_error(self, _: Any) -> None:
self.packet = (nick_to_pub_key("Alice") self.packet = (nick_to_pub_key("Alice")
+ SYMMETRIC_KEY_LENGTH * b'\x01' + SYMMETRIC_KEY_LENGTH * b'\x01'
+ SYMMETRIC_KEY_LENGTH * b'\x02' + SYMMETRIC_KEY_LENGTH * b'\x02'
@ -252,7 +252,7 @@ class TestKeyExPSKTx(TFCTestCase):
self.args = self.packet, self.ts, self.window_list, self.contact_list, self.key_list, self.settings self.args = self.packet, self.ts, self.window_list, self.contact_list, self.key_list, self.settings
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_invalid_nick_raises_se(self, _: Any) -> None: def test_invalid_nick_raises_soft_error(self, _: Any) -> None:
self.packet = (nick_to_pub_key("Alice") self.packet = (nick_to_pub_key("Alice")
+ SYMMETRIC_KEY_LENGTH * b'\x01' + SYMMETRIC_KEY_LENGTH * b'\x01'
+ bytes(SYMMETRIC_KEY_LENGTH) + bytes(SYMMETRIC_KEY_LENGTH)
@ -305,13 +305,13 @@ class TestKeyExPSKRx(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_unknown_account_raises_se(self) -> None: def test_unknown_account_raises_soft_error(self) -> None:
self.assert_se(f"Error: Unknown account '{nick_to_short_address('Bob')}'.", self.assert_se(f"Error: Unknown account '{nick_to_short_address('Bob')}'.",
key_ex_psk_rx, b'\x00' + nick_to_pub_key("Bob"), key_ex_psk_rx, b'\x00' + nick_to_pub_key("Bob"),
self.ts, self.window_list, self.contact_list, self.key_list, self.settings) self.ts, self.window_list, self.contact_list, self.key_list, self.settings)
@mock.patch('builtins.input', return_value=file_name) @mock.patch('builtins.input', return_value=file_name)
def test_invalid_psk_data_raises_se(self, _: Any) -> None: def test_invalid_psk_data_raises_soft_error(self, _: Any) -> None:
# Setup # Setup
with open(self.file_name, 'wb+') as f: with open(self.file_name, 'wb+') as f:
f.write(os.urandom(135)) f.write(os.urandom(135))
@ -321,7 +321,7 @@ class TestKeyExPSKRx(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', return_value=file_name) @mock.patch('builtins.input', return_value=file_name)
def test_permission_error_raises_se(self, *_: Any) -> None: def test_permission_error_raises_soft_error(self, *_: Any) -> None:
# Setup # Setup
with open(self.file_name, 'wb+') as f: with open(self.file_name, 'wb+') as f:
f.write(os.urandom(PSK_FILE_SIZE)) f.write(os.urandom(PSK_FILE_SIZE))
@ -423,7 +423,7 @@ class TestKeyExPSKRx(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=[file_name, '']) @mock.patch('builtins.input', side_effect=[file_name, ''])
@mock.patch('getpass.getpass', side_effect=[KeyboardInterrupt]) @mock.patch('getpass.getpass', side_effect=[KeyboardInterrupt])
def test_valid_psk_keyboard_interrupt_raises_se(self, *_: Any) -> None: def test_valid_psk_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None:
with open(self.file_name, 'wb+') as f: with open(self.file_name, 'wb+') as f:
f.write(bytes(PSK_FILE_SIZE)) f.write(bytes(PSK_FILE_SIZE))

View File

@ -38,8 +38,8 @@ from src.receiver.packet import PacketList
from src.receiver.windows import WindowList from src.receiver.windows import WindowList
from tests.mock_classes import ContactList, GroupList, KeyList, MasterKey, Settings from tests.mock_classes import ContactList, GroupList, KeyList, MasterKey, Settings
from tests.utils import assembly_packet_creator, cd_unit_test, cleanup, group_name_to_group_id from tests.utils import (assembly_packet_creator, cd_unit_test, cleanup, group_name_to_group_id,
from tests.utils import nick_to_pub_key, TFCTestCase nick_to_pub_key, TFCTestCase)
class TestProcessMessagePacket(TFCTestCase): class TestProcessMessagePacket(TFCTestCase):
@ -88,7 +88,7 @@ class TestProcessMessagePacket(TFCTestCase):
# Invalid packets # Invalid packets
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_invalid_origin_header_raises_se(self, _: Any) -> None: def test_invalid_origin_header_raises_soft_error(self, _: Any) -> None:
# Setup # Setup
invalid_origin_header = b'e' invalid_origin_header = b'e'
packet = nick_to_pub_key('Alice') + invalid_origin_header + MESSAGE_LENGTH * b'm' packet = nick_to_pub_key('Alice') + invalid_origin_header + MESSAGE_LENGTH * b'm'
@ -98,7 +98,7 @@ class TestProcessMessagePacket(TFCTestCase):
process_message_packet, self.ts, packet, *self.args) process_message_packet, self.ts, packet, *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_masqueraded_command_raises_se(self, _: Any) -> None: def test_masqueraded_command_raises_soft_error(self, _: Any) -> None:
for origin_header in [ORIGIN_USER_HEADER, ORIGIN_CONTACT_HEADER]: for origin_header in [ORIGIN_USER_HEADER, ORIGIN_CONTACT_HEADER]:
# Setup # Setup
packet = LOCAL_PUBKEY + origin_header + MESSAGE_LENGTH * b'm' packet = LOCAL_PUBKEY + origin_header + MESSAGE_LENGTH * b'm'
@ -172,7 +172,7 @@ class TestProcessMessagePacket(TFCTestCase):
# File key messages # File key messages
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_user_origin_raises_se(self, _: Any) -> None: def test_user_origin_raises_soft_error(self, _: Any) -> None:
assembly_ct_list = assembly_packet_creator(MESSAGE, ' ', origin_header=ORIGIN_USER_HEADER, assembly_ct_list = assembly_packet_creator(MESSAGE, ' ', origin_header=ORIGIN_USER_HEADER,
encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'),
message_header=FILE_KEY_HEADER) message_header=FILE_KEY_HEADER)
@ -181,7 +181,7 @@ class TestProcessMessagePacket(TFCTestCase):
self.assert_se("File key message from the user.", process_message_packet, self.ts, p, *self.args) self.assert_se("File key message from the user.", process_message_packet, self.ts, p, *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_invalid_file_key_data_raises_se(self, _: Any) -> None: def test_invalid_file_key_data_raises_soft_error(self, _: Any) -> None:
assembly_ct_list = assembly_packet_creator(MESSAGE, ' ', origin_header=ORIGIN_CONTACT_HEADER, assembly_ct_list = assembly_packet_creator(MESSAGE, ' ', origin_header=ORIGIN_CONTACT_HEADER,
encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'),
message_header=FILE_KEY_HEADER) message_header=FILE_KEY_HEADER)
@ -191,7 +191,7 @@ class TestProcessMessagePacket(TFCTestCase):
process_message_packet, self.ts, p, *self.args) process_message_packet, self.ts, p, *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_too_large_file_key_data_raises_se(self, _: Any) -> None: def test_too_large_file_key_data_raises_soft_error(self, _: Any) -> None:
assembly_ct_list = assembly_packet_creator(MESSAGE, base64.b85encode(BLAKE2_DIGEST_LENGTH * b'a' assembly_ct_list = assembly_packet_creator(MESSAGE, base64.b85encode(BLAKE2_DIGEST_LENGTH * b'a'
+ SYMMETRIC_KEY_LENGTH * b'b' + SYMMETRIC_KEY_LENGTH * b'b'
+ b'a').decode(), + b'a').decode(),
@ -216,7 +216,7 @@ class TestProcessMessagePacket(TFCTestCase):
# Group messages # Group messages
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_invalid_message_header_raises_se(self, _: Any) -> None: def test_invalid_message_header_raises_soft_error(self, _: Any) -> None:
# Setup # Setup
assembly_ct_list = assembly_packet_creator(MESSAGE, 'test_message', origin_header=ORIGIN_CONTACT_HEADER, assembly_ct_list = assembly_packet_creator(MESSAGE, 'test_message', origin_header=ORIGIN_CONTACT_HEADER,
encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'),
@ -227,7 +227,7 @@ class TestProcessMessagePacket(TFCTestCase):
process_message_packet, self.ts, assembly_ct_list[0], *self.args) process_message_packet, self.ts, assembly_ct_list[0], *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_invalid_window_raises_se(self, _: Any) -> None: def test_invalid_window_raises_soft_error(self, _: Any) -> None:
# Setup # Setup
assembly_ct_list = assembly_packet_creator(MESSAGE, 'test_message', origin_header=ORIGIN_CONTACT_HEADER, assembly_ct_list = assembly_packet_creator(MESSAGE, 'test_message', origin_header=ORIGIN_CONTACT_HEADER,
encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'),
@ -240,7 +240,7 @@ class TestProcessMessagePacket(TFCTestCase):
process_message_packet, self.ts, assembly_ct_list[0], *self.args) process_message_packet, self.ts, assembly_ct_list[0], *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_invalid_message_raises_se(self, _: Any) -> None: def test_invalid_message_raises_soft_error(self, _: Any) -> None:
# Setup # Setup
assembly_ct_list = assembly_packet_creator(MESSAGE, ' ', origin_header=ORIGIN_CONTACT_HEADER, assembly_ct_list = assembly_packet_creator(MESSAGE, ' ', origin_header=ORIGIN_CONTACT_HEADER,
encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'),
@ -251,7 +251,7 @@ class TestProcessMessagePacket(TFCTestCase):
process_message_packet, self.ts, assembly_ct_list[0], *self.args) process_message_packet, self.ts, assembly_ct_list[0], *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_invalid_whisper_header_raises_se(self, _: Any) -> None: def test_invalid_whisper_header_raises_soft_error(self, _: Any) -> None:
# Setup # Setup
assembly_ct_list = assembly_packet_creator(MESSAGE, '', origin_header=ORIGIN_CONTACT_HEADER, assembly_ct_list = assembly_packet_creator(MESSAGE, '', origin_header=ORIGIN_CONTACT_HEADER,
encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'),
@ -262,7 +262,7 @@ class TestProcessMessagePacket(TFCTestCase):
process_message_packet, self.ts, assembly_ct_list[0], *self.args) process_message_packet, self.ts, assembly_ct_list[0], *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
def test_contact_not_in_group_raises_se(self, _: Any) -> None: def test_contact_not_in_group_raises_soft_error(self, _: Any) -> None:
# Setup # Setup
assembly_ct_list = assembly_packet_creator(MESSAGE, 'test_message', origin_header=ORIGIN_CONTACT_HEADER, assembly_ct_list = assembly_packet_creator(MESSAGE, 'test_message', origin_header=ORIGIN_CONTACT_HEADER,

View File

@ -129,7 +129,7 @@ class TestOutputLoop(unittest.TestCase):
tx_pub_key) tx_pub_key)
tx_mk, tx_harac = rotate_key(tx_mk, tx_harac) tx_mk, tx_harac = rotate_key(tx_mk, tx_harac)
# ECDHE keyset for Bob # ECDHE keyset to Bob
command = (KEY_EX_ECDHE command = (KEY_EX_ECDHE
+ nick_to_pub_key("Bob") + nick_to_pub_key("Bob")
+ (4 * SYMMETRIC_KEY_LENGTH * b"a") + (4 * SYMMETRIC_KEY_LENGTH * b"a")
@ -138,7 +138,7 @@ class TestOutputLoop(unittest.TestCase):
local_key, local_harac = rotate_key(local_key, local_harac) local_key, local_harac = rotate_key(local_key, local_harac)
o_sleep(test_delay) o_sleep(test_delay)
# Message for Bob # Message to Bob
queue_packet(tx_mk, queue_packet(tx_mk,
tx_hk, tx_hk,
tx_harac, tx_harac,
@ -147,7 +147,7 @@ class TestOutputLoop(unittest.TestCase):
tx_mk, tx_harac = rotate_key(tx_mk, tx_harac) tx_mk, tx_harac = rotate_key(tx_mk, tx_harac)
o_sleep(test_delay) o_sleep(test_delay)
# Enable file reception for Bob # Enable file reception to Bob
command = CH_FILE_RECV + ENABLE.upper() + US_BYTE command = CH_FILE_RECV + ENABLE.upper() + US_BYTE
queue_packet(local_key, tx_hk, local_harac, command) queue_packet(local_key, tx_hk, local_harac, command)
o_sleep(test_delay) o_sleep(test_delay)

View File

@ -38,8 +38,8 @@ from src.transmitter.packet import split_to_assembly_packets
from src.receiver.packet import decrypt_assembly_packet, Packet, PacketList from src.receiver.packet import decrypt_assembly_packet, Packet, PacketList
from tests.mock_classes import ContactList, create_contact, KeyList, Settings, WindowList from tests.mock_classes import ContactList, create_contact, KeyList, Settings, WindowList
from tests.utils import assembly_packet_creator, cd_unit_test, cleanup, nick_to_pub_key, TFCTestCase from tests.utils import (assembly_packet_creator, cd_unit_test, cleanup, nick_to_pub_key, TFCTestCase,
from tests.utils import UNDECODABLE_UNICODE UNDECODABLE_UNICODE)
class TestDecryptAssemblyPacket(TFCTestCase): class TestDecryptAssemblyPacket(TFCTestCase):
@ -54,7 +54,7 @@ class TestDecryptAssemblyPacket(TFCTestCase):
self.keyset = self.key_list.get_keyset(nick_to_pub_key("Alice")) self.keyset = self.key_list.get_keyset(nick_to_pub_key("Alice"))
self.args = self.onion_pub_key, self.origin, self.window_list, self.contact_list, self.key_list self.args = self.onion_pub_key, self.origin, self.window_list, self.contact_list, self.key_list
def test_decryption_with_zero_rx_key_raises_se(self) -> None: def test_decryption_with_zero_rx_key_raises_soft_error(self) -> None:
# Setup # Setup
keyset = self.key_list.get_keyset(nick_to_pub_key("Alice")) keyset = self.key_list.get_keyset(nick_to_pub_key("Alice"))
keyset.rx_mk = bytes(SYMMETRIC_KEY_LENGTH) keyset.rx_mk = bytes(SYMMETRIC_KEY_LENGTH)
@ -64,12 +64,12 @@ class TestDecryptAssemblyPacket(TFCTestCase):
self.assert_se("Warning! Loaded zero-key for packet decryption.", self.assert_se("Warning! Loaded zero-key for packet decryption.",
decrypt_assembly_packet, packet, *self.args) decrypt_assembly_packet, packet, *self.args)
def test_invalid_harac_ct_raises_se(self) -> None: def test_invalid_harac_ct_raises_soft_error(self) -> None:
packet = assembly_packet_creator(MESSAGE, payload="Test message", encrypt_packet=True, tamper_harac=True)[0] packet = assembly_packet_creator(MESSAGE, payload="Test message", encrypt_packet=True, tamper_harac=True)[0]
self.assert_se("Warning! Received packet from Alice had an invalid hash ratchet MAC.", self.assert_se("Warning! Received packet from Alice had an invalid hash ratchet MAC.",
decrypt_assembly_packet, packet, *self.args) decrypt_assembly_packet, packet, *self.args)
def test_decryption_with_zero_rx_hek_raises_se(self) -> None: def test_decryption_with_zero_rx_hek_raises_soft_error(self) -> None:
# Setup # Setup
keyset = self.key_list.get_keyset(nick_to_pub_key("Alice")) keyset = self.key_list.get_keyset(nick_to_pub_key("Alice"))
keyset.rx_hk = bytes(SYMMETRIC_KEY_LENGTH) keyset.rx_hk = bytes(SYMMETRIC_KEY_LENGTH)
@ -78,7 +78,7 @@ class TestDecryptAssemblyPacket(TFCTestCase):
# Test # Test
self.assert_se("Warning! Loaded zero-key for packet decryption.", decrypt_assembly_packet, packet, *self.args) self.assert_se("Warning! Loaded zero-key for packet decryption.", decrypt_assembly_packet, packet, *self.args)
def test_expired_harac_raises_se(self) -> None: def test_expired_harac_raises_soft_error(self) -> None:
# Setup # Setup
self.keyset.rx_harac = 1 self.keyset.rx_harac = 1
@ -93,7 +93,7 @@ class TestDecryptAssemblyPacket(TFCTestCase):
self.assert_se("Dropped packet from Alice.", self.assert_se("Dropped packet from Alice.",
decrypt_assembly_packet, packet, *self.args) decrypt_assembly_packet, packet, *self.args)
def test_invalid_packet_ct_raises_se(self) -> None: def test_invalid_packet_ct_raises_soft_error(self) -> None:
packet = assembly_packet_creator(MESSAGE, payload="Test message", encrypt_packet=True, tamper_message=True)[0] packet = assembly_packet_creator(MESSAGE, payload="Test message", encrypt_packet=True, tamper_message=True)[0]
self.assert_se("Warning! Received packet from Alice had an invalid MAC.", self.assert_se("Warning! Received packet from Alice had an invalid MAC.",
decrypt_assembly_packet, packet, *self.args) decrypt_assembly_packet, packet, *self.args)
@ -148,7 +148,7 @@ class TestPacket(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_invalid_assembly_packet_header_raises_se(self) -> None: def test_invalid_assembly_packet_header_raises_soft_error(self) -> None:
# Setup # Setup
packet = Packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE, self.contact, self.settings) packet = Packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE, self.contact, self.settings)
a_packet = assembly_packet_creator(MESSAGE, payload=self.short_msg, s_header_override=b'i')[0] a_packet = assembly_packet_creator(MESSAGE, payload=self.short_msg, s_header_override=b'i')[0]
@ -157,7 +157,7 @@ class TestPacket(TFCTestCase):
self.assert_se("Error: Received packet had an invalid assembly packet header.", packet.add_packet, a_packet) self.assert_se("Error: Received packet had an invalid assembly packet header.", packet.add_packet, a_packet)
self.assertEqual(packet.log_masking_ctr, 1) self.assertEqual(packet.log_masking_ctr, 1)
def test_missing_start_packet_raises_se(self) -> None: def test_missing_start_packet_raises_soft_error(self) -> None:
# Setup # Setup
packet = Packet(self.onion_pub_key, ORIGIN_USER_HEADER, MESSAGE, self.contact, self.settings) packet = Packet(self.onion_pub_key, ORIGIN_USER_HEADER, MESSAGE, self.contact, self.settings)
@ -179,7 +179,7 @@ class TestPacket(TFCTestCase):
self.whisper_header + PRIVATE_MESSAGE_HEADER + self.short_msg.encode()) self.whisper_header + PRIVATE_MESSAGE_HEADER + self.short_msg.encode())
self.assertEqual(packet.log_ct_list, [b'test_ct']) self.assertEqual(packet.log_ct_list, [b'test_ct'])
def test_compression_error_raises_se(self) -> None: def test_compression_error_raises_soft_error(self) -> None:
# Setup # Setup
packet = Packet(self.onion_pub_key, ORIGIN_USER_HEADER, MESSAGE, self.contact, self.settings) packet = Packet(self.onion_pub_key, ORIGIN_USER_HEADER, MESSAGE, self.contact, self.settings)
packet_list = assembly_packet_creator(MESSAGE, self.short_msg, tamper_compression=True) packet_list = assembly_packet_creator(MESSAGE, self.short_msg, tamper_compression=True)
@ -203,7 +203,7 @@ class TestPacket(TFCTestCase):
self.assertEqual(message, self.whisper_header + PRIVATE_MESSAGE_HEADER + self.msg.encode()) self.assertEqual(message, self.whisper_header + PRIVATE_MESSAGE_HEADER + self.msg.encode())
self.assertEqual(packet.log_ct_list, 3 * [b'test_ct']) self.assertEqual(packet.log_ct_list, 3 * [b'test_ct'])
def test_decryption_error_raises_se(self) -> None: def test_decryption_error_raises_soft_error(self) -> None:
# Setup # Setup
packet = Packet(self.onion_pub_key, ORIGIN_USER_HEADER, MESSAGE, self.contact, self.settings) packet = Packet(self.onion_pub_key, ORIGIN_USER_HEADER, MESSAGE, self.contact, self.settings)
packet_list = assembly_packet_creator(MESSAGE, self.msg, tamper_ciphertext=True) packet_list = assembly_packet_creator(MESSAGE, self.msg, tamper_ciphertext=True)
@ -235,7 +235,7 @@ class TestPacket(TFCTestCase):
self.assertIsNone(packet.assemble_and_store_file(self.ts, self.onion_pub_key, self.window_list)) self.assertIsNone(packet.assemble_and_store_file(self.ts, self.onion_pub_key, self.window_list))
self.assertTrue(os.path.isfile(f'{DIR_RECV_FILES}Alice/testfile.txt.1')) self.assertTrue(os.path.isfile(f'{DIR_RECV_FILES}Alice/testfile.txt.1'))
def test_short_file_from_user_raises_se(self) -> None: def test_short_file_from_user_raises_soft_error(self) -> None:
# Setup # Setup
packet = Packet(self.onion_pub_key, ORIGIN_USER_HEADER, FILE, self.contact, self.settings) packet = Packet(self.onion_pub_key, ORIGIN_USER_HEADER, FILE, self.contact, self.settings)
packets = split_to_assembly_packets(self.short_f_data, FILE) packets = split_to_assembly_packets(self.short_f_data, FILE)
@ -245,7 +245,7 @@ class TestPacket(TFCTestCase):
self.assert_se("Ignored file from the user.", packet.add_packet, p) self.assert_se("Ignored file from the user.", packet.add_packet, p)
self.assertEqual(packet.log_masking_ctr, 1) self.assertEqual(packet.log_masking_ctr, 1)
def test_unauthorized_file_from_contact_raises_se(self) -> None: def test_unauthorized_file_from_contact_raises_soft_error(self) -> None:
# Setup # Setup
self.contact.file_reception = False self.contact.file_reception = False
@ -305,7 +305,7 @@ class TestPacket(TFCTestCase):
self.assert_se("Alert! File reception disabled mid-transfer.", packet.add_packet, p) self.assert_se("Alert! File reception disabled mid-transfer.", packet.add_packet, p)
self.assertEqual(packet.log_masking_ctr, len(packet_list)) self.assertEqual(packet.log_masking_ctr, len(packet_list))
def test_long_file_from_user_raises_se(self) -> None: def test_long_file_from_user_raises_soft_error(self) -> None:
# Setup # Setup
packet = Packet(self.onion_pub_key, ORIGIN_USER_HEADER, FILE, self.contact, self.settings) packet = Packet(self.onion_pub_key, ORIGIN_USER_HEADER, FILE, self.contact, self.settings)
packet_list = assembly_packet_creator(FILE) packet_list = assembly_packet_creator(FILE)
@ -314,7 +314,7 @@ class TestPacket(TFCTestCase):
self.assert_se("Ignored file from the user.", packet.add_packet, packet_list[0]) self.assert_se("Ignored file from the user.", packet.add_packet, packet_list[0])
self.assertEqual(packet.log_masking_ctr, 1) self.assertEqual(packet.log_masking_ctr, 1)
def test_unauthorized_long_file_raises_se(self) -> None: def test_unauthorized_long_file_raises_soft_error(self) -> None:
# Setup # Setup
self.contact.file_reception = False self.contact.file_reception = False
@ -326,7 +326,7 @@ class TestPacket(TFCTestCase):
packet.add_packet, packet_list[0]) packet.add_packet, packet_list[0])
self.assertEqual(packet.log_masking_ctr, 1) self.assertEqual(packet.log_masking_ctr, 1)
def test_invalid_long_file_header_raises_se(self) -> None: def test_invalid_long_file_header_raises_soft_error(self) -> None:
# Setup # Setup
packet = Packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, FILE, self.contact, self.settings) packet = Packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, FILE, self.contact, self.settings)
packet_list = assembly_packet_creator(FILE, file_name=UNDECODABLE_UNICODE) packet_list = assembly_packet_creator(FILE, file_name=UNDECODABLE_UNICODE)
@ -390,7 +390,7 @@ class TestPacket(TFCTestCase):
self.assertEqual(packet.assemble_command_packet(), command) self.assertEqual(packet.assemble_command_packet(), command)
self.assertEqual(packet.log_masking_ctr, 0) self.assertEqual(packet.log_masking_ctr, 0)
def test_long_command_hash_mismatch_raises_se(self) -> None: def test_long_command_hash_mismatch_raises_soft_error(self) -> None:
# Setup # Setup
packet = Packet(LOCAL_ID, ORIGIN_CONTACT_HEADER, COMMAND, self.contact, self.settings) packet = Packet(LOCAL_ID, ORIGIN_CONTACT_HEADER, COMMAND, self.contact, self.settings)
packet_list = assembly_packet_creator(COMMAND, os.urandom(500), tamper_cmd_hash=True) packet_list = assembly_packet_creator(COMMAND, os.urandom(500), tamper_cmd_hash=True)
@ -402,7 +402,7 @@ class TestPacket(TFCTestCase):
self.assert_se("Error: Received an invalid command.", packet.assemble_command_packet) self.assert_se("Error: Received an invalid command.", packet.assemble_command_packet)
self.assertEqual(packet.log_masking_ctr, 0) self.assertEqual(packet.log_masking_ctr, 0)
def test_long_command_compression_error_raises_se(self) -> None: def test_long_command_compression_error_raises_soft_error(self) -> None:
# Setup # Setup
packet = Packet(LOCAL_ID, ORIGIN_CONTACT_HEADER, COMMAND, self.contact, self.settings) packet = Packet(LOCAL_ID, ORIGIN_CONTACT_HEADER, COMMAND, self.contact, self.settings)
packet_list = assembly_packet_creator(COMMAND, os.urandom(500), tamper_compression=True) packet_list = assembly_packet_creator(COMMAND, os.urandom(500), tamper_compression=True)

View File

@ -76,7 +76,7 @@ class TestRxWindow(TFCTestCase):
self.assertEqual(window.window_contacts[0].onion_pub_key, nick_to_pub_key("Alice")) self.assertEqual(window.window_contacts[0].onion_pub_key, nick_to_pub_key("Alice"))
self.assertEqual(window.name, 'test_group') self.assertEqual(window.name, 'test_group')
def test_invalid_uid_raises_se(self) -> None: def test_invalid_uid_raises_soft_error(self) -> None:
self.assert_se("Invalid window 'mfqwcylbmfqwcylbmfqwcylbmfqwcylbmfqwcylbmfqwcylbmfqwbfad'.", self.assert_se("Invalid window 'mfqwcylbmfqwcylbmfqwcylbmfqwcylbmfqwcylbmfqwcylbmfqwbfad'.",
self.create_window, ONION_SERVICE_PUBLIC_KEY_LENGTH * b'a') self.create_window, ONION_SERVICE_PUBLIC_KEY_LENGTH * b'a')

View File

@ -136,11 +136,11 @@ class TestChangeECRatio(TFCTestCase):
"""Pre-test actions.""" """Pre-test actions."""
self.gateway = Gateway() self.gateway = Gateway()
def test_non_digit_value_raises_se(self) -> None: def test_non_digit_value_raises_soft_error(self) -> None:
self.assert_se("Error: Received invalid EC ratio value from Transmitter Program.", self.assert_se("Error: Received invalid EC ratio value from Transmitter Program.",
change_ec_ratio, b'a', self.gateway) change_ec_ratio, b'a', self.gateway)
def test_invalid_digit_value_raises_se(self) -> None: def test_invalid_digit_value_raises_soft_error(self) -> None:
self.assert_se("Error: Received invalid EC ratio value from Transmitter Program.", self.assert_se("Error: Received invalid EC ratio value from Transmitter Program.",
change_ec_ratio, b'-1', self.gateway) change_ec_ratio, b'-1', self.gateway)
@ -155,11 +155,11 @@ class TestChangeBaudrate(TFCTestCase):
"""Pre-test actions.""" """Pre-test actions."""
self.gateway = Gateway() self.gateway = Gateway()
def test_non_digit_value_raises_se(self) -> None: def test_non_digit_value_raises_soft_error(self) -> None:
self.assert_se("Error: Received invalid baud rate value from Transmitter Program.", self.assert_se("Error: Received invalid baud rate value from Transmitter Program.",
change_baudrate, b'a', self.gateway) change_baudrate, b'a', self.gateway)
def test_invalid_digit_value_raises_se(self) -> None: def test_invalid_digit_value_raises_soft_error(self) -> None:
self.assert_se("Error: Received invalid baud rate value from Transmitter Program.", self.assert_se("Error: Received invalid baud rate value from Transmitter Program.",
change_baudrate, b'1300', self.gateway) change_baudrate, b'1300', self.gateway)
@ -216,7 +216,7 @@ class TestAddContact(unittest.TestCase):
def test_add_contact(self) -> None: def test_add_contact(self) -> None:
command = b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')]) command = b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')])
self.assertIsNone(add_contact(command, True, self.queues)) self.assertIsNone(add_contact(command, self.queues, True))
self.assertEqual(self.queues[CONTACT_MGMT_QUEUE].qsize(), 1) self.assertEqual(self.queues[CONTACT_MGMT_QUEUE].qsize(), 1)
for q in [GROUP_MGMT_QUEUE, C_REQ_MGMT_QUEUE]: for q in [GROUP_MGMT_QUEUE, C_REQ_MGMT_QUEUE]:
command = self.queues[q].get() command = self.queues[q].get()

View File

@ -107,9 +107,9 @@ class TestPubKeyChecker(unittest.TestCase):
@mock.patch('shutil.get_terminal_size', return_value=[200, 200]) @mock.patch('shutil.get_terminal_size', return_value=[200, 200])
def test_pub_key_checker(self, _: Any) -> None: def test_pub_key_checker(self, _: Any) -> None:
# Setup # Setup
public_key = TFC_PUBLIC_KEY_LENGTH*b'a' public_key = TFC_PUBLIC_KEY_LENGTH*b'a'
invalid_public_key = b58encode(public_key, public_key=True)[:-1] + 'a' invalid_public_key = b58encode(public_key, public_key=True)[:-1] + 'a'
account = nick_to_pub_key('Bob') account = nick_to_pub_key('Bob')
for local_test in [True, False]: for local_test in [True, False]:
self.queues[PUB_KEY_SEND_QUEUE].put((account, public_key)) self.queues[PUB_KEY_SEND_QUEUE].put((account, public_key))

View File

@ -38,7 +38,7 @@ class TestFlaskServer(unittest.TestCase):
url_token_public_key = X448.derive_public_key(url_token_private_key).hex() url_token_public_key = X448.derive_public_key(url_token_private_key).hex()
url_token = 'a450987345098723459870234509827340598273405983274234098723490285' url_token = 'a450987345098723459870234509827340598273405983274234098723490285'
url_token_old = 'a450987345098723459870234509827340598273405983274234098723490286' url_token_old = 'a450987345098723459870234509827340598273405983274234098723490286'
url_token_invalid = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' url_token_invalid = 'ääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääää'
onion_pub_key = nick_to_pub_key('Alice') onion_pub_key = nick_to_pub_key('Alice')
onion_address = nick_to_onion_address('Alice') onion_address = nick_to_onion_address('Alice')
packet1 = "packet1" packet1 = "packet1"
@ -48,6 +48,13 @@ class TestFlaskServer(unittest.TestCase):
# Test # Test
app = flask_server(queues, url_token_public_key, unit_test=True) app = flask_server(queues, url_token_public_key, unit_test=True)
# Test valid URL token returns all queued messages
queues[URL_TOKEN_QUEUE].put((onion_pub_key, url_token_old))
queues[URL_TOKEN_QUEUE].put((onion_pub_key, url_token))
queues[M_TO_FLASK_QUEUE].put((packet1, onion_pub_key))
queues[M_TO_FLASK_QUEUE].put((packet2, onion_pub_key))
queues[F_TO_FLASK_QUEUE].put((packet3, onion_pub_key))
with app.test_client() as c: with app.test_client() as c:
# Test root domain returns public key of server. # Test root domain returns public key of server.
resp = c.get('/') resp = c.get('/')
@ -63,13 +70,6 @@ class TestFlaskServer(unittest.TestCase):
resp = c.get(f'/{url_token_invalid}/files/') resp = c.get(f'/{url_token_invalid}/files/')
self.assertEqual(b'', resp.data) self.assertEqual(b'', resp.data)
# Test valid URL token returns all queued messages
queues[URL_TOKEN_QUEUE].put((onion_pub_key, url_token_old))
queues[URL_TOKEN_QUEUE].put((onion_pub_key, url_token))
queues[M_TO_FLASK_QUEUE].put((packet1, onion_pub_key))
queues[M_TO_FLASK_QUEUE].put((packet2, onion_pub_key))
queues[F_TO_FLASK_QUEUE].put((packet3, onion_pub_key))
with app.test_client() as c: with app.test_client() as c:
resp = c.get(f'/{url_token}/messages/') resp = c.get(f'/{url_token}/messages/')
self.assertEqual(b'packet1\npacket2', resp.data) self.assertEqual(b'packet1\npacket2', resp.data)

View File

@ -28,9 +28,9 @@ from unittest import mock
from unittest.mock import MagicMock from unittest.mock import MagicMock
from typing import Any from typing import Any
from src.common.database import TFCDatabase, MessageLog from src.common.database import TFCDatabase, MessageLog
from src.common.db_logs import write_log_entry from src.common.db_logs import write_log_entry
from src.common.encoding import bool_to_bytes from src.common.encoding import bool_to_bytes
from src.common.db_masterkey import MasterKey as OrigMasterKey from src.common.db_masterkey import MasterKey as OrigMasterKey
from src.common.statics import (BOLD_ON, CLEAR_ENTIRE_SCREEN, COMMAND_PACKET_QUEUE, CURSOR_LEFT_UP_CORNER, from src.common.statics import (BOLD_ON, CLEAR_ENTIRE_SCREEN, COMMAND_PACKET_QUEUE, CURSOR_LEFT_UP_CORNER,
DIR_USER_DATA, KEY_MGMT_ACK_QUEUE, KEX_STATUS_NO_RX_PSK, KEX_STATUS_UNVERIFIED, DIR_USER_DATA, KEY_MGMT_ACK_QUEUE, KEX_STATUS_NO_RX_PSK, KEX_STATUS_UNVERIFIED,
@ -41,16 +41,16 @@ from src.common.statics import (BOLD_ON, CLEAR_ENTIRE_SCREEN, COMMAND_PACKE
UNENCRYPTED_WIPE_COMMAND, VERSION, WIN_TYPE_CONTACT, WIN_TYPE_GROUP, UNENCRYPTED_WIPE_COMMAND, VERSION, WIN_TYPE_CONTACT, WIN_TYPE_GROUP,
KDB_HALT_ACK_HEADER, KDB_M_KEY_CHANGE_HALT_HEADER) KDB_HALT_ACK_HEADER, KDB_M_KEY_CHANGE_HALT_HEADER)
from src.transmitter.commands import change_master_key, change_setting, clear_screens, exit_tfc, log_command from src.transmitter.commands import (change_master_key, change_setting, clear_screens, exit_tfc, log_command,
from src.transmitter.commands import print_about, print_help, print_recipients, print_settings, process_command print_about, print_help, print_recipients, print_settings, process_command,
from src.transmitter.commands import remove_log, rxp_display_unread, rxp_show_sys_win, send_onion_service_key, verify remove_log, rxp_display_unread, rxp_show_sys_win, send_onion_service_key,
from src.transmitter.commands import whisper, whois, wipe verify, whisper, whois, wipe)
from src.transmitter.packet import split_to_assembly_packets from src.transmitter.packet import split_to_assembly_packets
from tests.mock_classes import ContactList, create_contact, Gateway, GroupList, MasterKey, OnionService, Settings from tests.mock_classes import (ContactList, create_contact, Gateway, GroupList, MasterKey, OnionService, Settings,
from tests.mock_classes import TxWindow, UserInput TxWindow, UserInput)
from tests.utils import assembly_packet_creator, cd_unit_test, cleanup, group_name_to_group_id from tests.utils import (assembly_packet_creator, cd_unit_test, cleanup, group_name_to_group_id, gen_queue_dict,
from tests.utils import gen_queue_dict, nick_to_onion_address, nick_to_pub_key, tear_queues, TFCTestCase nick_to_onion_address, nick_to_pub_key, tear_queues, TFCTestCase)
class TestProcessCommand(TFCTestCase): class TestProcessCommand(TFCTestCase):
@ -268,17 +268,17 @@ class TestLogCommand(TFCTestCase):
log_command, UserInput("history"), *self.args) log_command, UserInput("history"), *self.args)
self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 1) self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 1)
def test_invalid_number_raises_se(self) -> None: def test_invalid_number_raises_soft_error(self) -> None:
self.assert_se("Error: Invalid number of messages.", self.assert_se("Error: Invalid number of messages.",
log_command, UserInput('history a'), *self.args) log_command, UserInput('history a'), *self.args)
def test_too_high_number_raises_se(self) -> None: def test_too_high_number_raises_soft_error(self) -> None:
self.assert_se("Error: Invalid number of messages.", self.assert_se("Error: Invalid number of messages.",
log_command, UserInput('history 94857634985763454345'), *self.args) log_command, UserInput('history 94857634985763454345'), *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', return_value='No') @mock.patch('builtins.input', return_value='No')
def test_user_abort_raises_se(self, *_: Any) -> None: def test_user_abort_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Log file export aborted.", self.assert_se("Log file export aborted.",
log_command, UserInput('export'), *self.args) log_command, UserInput('export'), *self.args)
@ -290,7 +290,7 @@ class TestLogCommand(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', return_value='Yes') @mock.patch('builtins.input', return_value='Yes')
@mock.patch('getpass.getpass', side_effect=['test_password', 'test_password', KeyboardInterrupt]) @mock.patch('getpass.getpass', side_effect=['test_password', 'test_password', KeyboardInterrupt])
def test_keyboard_interrupt_raises_se(self, *_: Any) -> None: def test_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None:
self.master_key = OrigMasterKey(operation=TX, local_test=True) self.master_key = OrigMasterKey(operation=TX, local_test=True)
self.assert_se("Authentication aborted.", self.assert_se("Authentication aborted.",
log_command, UserInput('export'), *self.args) log_command, UserInput('export'), *self.args)
@ -543,12 +543,12 @@ class TestChangeMasterKey(TFCTestCase):
self.assert_se("Error: Command is disabled during traffic masking.", self.assert_se("Error: Command is disabled during traffic masking.",
change_master_key, UserInput(), *self.args) change_master_key, UserInput(), *self.args)
def test_missing_target_sys_raises_se(self) -> None: def test_missing_target_sys_raises_soft_error(self) -> None:
self.assert_se("Error: No target-system ('tx' or 'rx') specified.", self.assert_se("Error: No target-system ('tx' or 'rx') specified.",
change_master_key, UserInput("passwd "), *self.args) change_master_key, UserInput("passwd "), *self.args)
@mock.patch('getpass.getpass', return_value='test_password') @mock.patch('getpass.getpass', return_value='test_password')
def test_invalid_target_sys_raises_se(self, _: Any) -> None: def test_invalid_target_sys_raises_soft_error(self, _: Any) -> None:
self.assert_se("Error: Invalid target system 't'.", self.assert_se("Error: Invalid target system 't'.",
change_master_key, UserInput("passwd t"), *self.args) change_master_key, UserInput("passwd t"), *self.args)
@ -557,7 +557,7 @@ class TestChangeMasterKey(TFCTestCase):
@mock.patch('getpass.getpass', side_effect=['test_password', 'a', 'a']) @mock.patch('getpass.getpass', side_effect=['test_password', 'a', 'a'])
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 0.01) @mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 0.01)
def test_invalid_response_from_key_db_raises_se(self, *_: Any) -> None: def test_invalid_response_from_key_db_raises_soft_error(self, *_: Any) -> None:
# Setup # Setup
def mock_sender_loop() -> None: def mock_sender_loop() -> None:
"""Mock sender loop key management functionality.""" """Mock sender loop key management functionality."""
@ -581,7 +581,7 @@ class TestChangeMasterKey(TFCTestCase):
@mock.patch('getpass.getpass', side_effect=['test_password', 'a', 'a']) @mock.patch('getpass.getpass', side_effect=['test_password', 'a', 'a'])
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 0.01) @mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 0.01)
def test_transmitter_command_raises_system_exit_if_key_database_returns_invalid_master_key(self, *_: Any) -> None: def test_transmitter_command_raises_critical_error_if_key_database_returns_invalid_master_key(self, *_: Any) -> None:
# Setup # Setup
def mock_sender_loop() -> None: def mock_sender_loop() -> None:
"""Mock sender loop key management functionality.""" """Mock sender loop key management functionality."""
@ -693,7 +693,7 @@ class TestChangeMasterKey(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('getpass.getpass', side_effect=KeyboardInterrupt) @mock.patch('getpass.getpass', side_effect=KeyboardInterrupt)
def test_keyboard_interrupt_raises_se(self, *_: Any) -> None: def test_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Authentication aborted.", change_master_key, UserInput("passwd tx"), *self.args) self.assert_se("Authentication aborted.", change_master_key, UserInput("passwd tx"), *self.args)
@ -717,7 +717,7 @@ class TestRemoveLog(TFCTestCase):
tear_queues(self.queues) tear_queues(self.queues)
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_missing_contact_raises_se(self) -> None: def test_missing_contact_raises_soft_error(self) -> None:
self.assert_se("Error: No contact/group specified.", self.assert_se("Error: No contact/group specified.",
remove_log, UserInput(''), *self.args) remove_log, UserInput(''), *self.args)
@ -734,12 +734,12 @@ class TestRemoveLog(TFCTestCase):
@mock.patch('shutil.get_terminal_size', return_value=[150, 150]) @mock.patch('shutil.get_terminal_size', return_value=[150, 150])
@mock.patch('builtins.input', return_value='Yes') @mock.patch('builtins.input', return_value='Yes')
def test_removal_with_invalid_account_raises_se(self, *_: Any) -> None: def test_removal_with_invalid_account_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Error: Invalid account.", self.assert_se("Error: Invalid account.",
remove_log, UserInput(f'/rmlogs {nick_to_onion_address("Alice")[:-1] + "a"}'), *self.args) remove_log, UserInput(f'/rmlogs {nick_to_onion_address("Alice")[:-1] + "a"}'), *self.args)
@mock.patch('builtins.input', return_value='Yes') @mock.patch('builtins.input', return_value='Yes')
def test_invalid_group_id_raises_se(self, _: Any) -> None: def test_invalid_group_id_raises_soft_error(self, _: Any) -> None:
self.assert_se("Error: Invalid group ID.", self.assert_se("Error: Invalid group ID.",
remove_log, UserInput(f'/rmlogs {group_name_to_group_id("test_group")[:-1] + b"a"}'), *self.args) remove_log, UserInput(f'/rmlogs {group_name_to_group_id("test_group")[:-1] + b"a"}'), *self.args)
@ -788,7 +788,7 @@ class TestRemoveLog(TFCTestCase):
self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 1) self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 1)
@mock.patch('builtins.input', return_value='Yes') @mock.patch('builtins.input', return_value='Yes')
def test_unknown_selector_raises_se(self, _: Any) -> None: def test_unknown_selector_raises_soft_error(self, _: Any) -> None:
# Setup # Setup
write_log_entry(M_S_HEADER + PADDING_LENGTH * b'a', nick_to_pub_key("Alice"), self.tfc_log_database) write_log_entry(M_S_HEADER + PADDING_LENGTH * b'a', nick_to_pub_key("Alice"), self.tfc_log_database)
@ -814,15 +814,15 @@ class TestChangeSetting(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
tear_queues(self.queues) tear_queues(self.queues)
def test_missing_setting_raises_se(self) -> None: def test_missing_setting_raises_soft_error(self) -> None:
self.assert_se("Error: No setting specified.", self.assert_se("Error: No setting specified.",
change_setting, UserInput('set'), *self.args) change_setting, UserInput('set'), *self.args)
def test_invalid_setting_raises_se(self) -> None: def test_invalid_setting_raises_soft_error(self) -> None:
self.assert_se("Error: Invalid setting 'e_correction_ratia'.", self.assert_se("Error: Invalid setting 'e_correction_ratia'.",
change_setting, UserInput("set e_correction_ratia true"), *self.args) change_setting, UserInput("set e_correction_ratia true"), *self.args)
def test_missing_value_raises_se(self) -> None: def test_missing_value_raises_soft_error(self) -> None:
self.assert_se("Error: No value for setting specified.", self.assert_se("Error: No value for setting specified.",
change_setting, UserInput("set serial_error_correction"), *self.args) change_setting, UserInput("set serial_error_correction"), *self.args)
@ -1027,11 +1027,11 @@ class TestVerify(TFCTestCase):
self.window.contact = self.contact self.window.contact = self.contact
self.args = self.window, self.contact_list self.args = self.window, self.contact_list
def test_active_group_raises_se(self) -> None: def test_active_group_raises_soft_error(self) -> None:
self.window.type = WIN_TYPE_GROUP self.window.type = WIN_TYPE_GROUP
self.assert_se("Error: A group is selected.", verify, *self.args) self.assert_se("Error: A group is selected.", verify, *self.args)
def test_psk_raises_se(self) -> None: def test_psk_raises_soft_error(self) -> None:
self.contact.kex_status = KEX_STATUS_NO_RX_PSK self.contact.kex_status = KEX_STATUS_NO_RX_PSK
self.assert_se("Pre-shared keys have no fingerprints.", verify, *self.args) self.assert_se("Pre-shared keys have no fingerprints.", verify, *self.args)
@ -1048,7 +1048,7 @@ class TestVerify(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=KeyboardInterrupt) @mock.patch('builtins.input', side_effect=KeyboardInterrupt)
def test_keyboard_interrupt_raises_se(self, *_: Any) -> None: def test_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None:
self.contact.kex_status = KEX_STATUS_VERIFIED self.contact.kex_status = KEX_STATUS_VERIFIED
self.assert_se("Fingerprint verification aborted.", verify, *self.args) self.assert_se("Fingerprint verification aborted.", verify, *self.args)
self.assertEqual(self.contact.kex_status, KEX_STATUS_VERIFIED) self.assertEqual(self.contact.kex_status, KEX_STATUS_VERIFIED)
@ -1066,7 +1066,7 @@ class TestWhisper(TFCTestCase):
self.queues = gen_queue_dict() self.queues = gen_queue_dict()
self.args = self.window, self.settings, self.queues self.args = self.window, self.settings, self.queues
def test_empty_input_raises_se(self) -> None: def test_empty_input_raises_soft_error(self) -> None:
self.assert_se("Error: No whisper message specified.", self.assert_se("Error: No whisper message specified.",
whisper, UserInput("whisper"), *self.args) whisper, UserInput("whisper"), *self.args)
@ -1087,10 +1087,10 @@ class TestWhois(TFCTestCase):
self.group_list = GroupList(groups=['test_group']) self.group_list = GroupList(groups=['test_group'])
self.args = self.contact_list, self.group_list self.args = self.contact_list, self.group_list
def test_missing_selector_raises_se(self) -> None: def test_missing_selector_raises_soft_error(self) -> None:
self.assert_se("Error: No account or nick specified.", whois, UserInput("whois"), *self.args) self.assert_se("Error: No account or nick specified.", whois, UserInput("whois"), *self.args)
def test_unknown_account_raises_se(self) -> None: def test_unknown_account_raises_soft_error(self) -> None:
self.assert_se("Error: Unknown selector.", whois, UserInput("whois alice"), *self.args) self.assert_se("Error: Unknown selector.", whois, UserInput("whois alice"), *self.args)
def test_nick_from_account(self) -> None: def test_nick_from_account(self) -> None:
@ -1132,7 +1132,7 @@ class TestWipe(TFCTestCase):
self.args = self.settings, self.queues, self.gateway self.args = self.settings, self.queues, self.gateway
@mock.patch('builtins.input', return_value='No') @mock.patch('builtins.input', return_value='No')
def test_no_raises_se(self, _: Any) -> None: def test_no_raises_soft_error(self, _: Any) -> None:
self.assert_se("Wipe command aborted.", wipe, *self.args) self.assert_se("Wipe command aborted.", wipe, *self.args)
@mock.patch('os.system', return_value=None) @mock.patch('os.system', return_value=None)

View File

@ -28,8 +28,8 @@ from src.common.encoding import b58encode
from src.common.statics import (COMMAND_PACKET_QUEUE, GROUP_ID_LENGTH, RELAY_PACKET_QUEUE, from src.common.statics import (COMMAND_PACKET_QUEUE, GROUP_ID_LENGTH, RELAY_PACKET_QUEUE,
WIN_TYPE_CONTACT, WIN_TYPE_GROUP) WIN_TYPE_CONTACT, WIN_TYPE_GROUP)
from src.transmitter.commands_g import group_add_member, group_create, group_rm_group, group_rm_member from src.transmitter.commands_g import (group_add_member, group_create, group_rm_group, group_rm_member,
from src.transmitter.commands_g import process_group_command, group_rename process_group_command, group_rename)
from tests.mock_classes import create_group, Contact, ContactList, GroupList, MasterKey, Settings, UserInput, TxWindow from tests.mock_classes import create_group, Contact, ContactList, GroupList, MasterKey, Settings, UserInput, TxWindow
from tests.utils import cd_unit_test, cleanup, gen_queue_dict, nick_to_pub_key, tear_queues, TFCTestCase from tests.utils import cd_unit_test, cleanup, gen_queue_dict, nick_to_pub_key, tear_queues, TFCTestCase
@ -58,19 +58,19 @@ class TestProcessGroupCommand(TFCTestCase):
self.assert_se("Error: Command is disabled during traffic masking.", self.assert_se("Error: Command is disabled during traffic masking.",
process_group_command, UserInput(), *self.args) process_group_command, UserInput(), *self.args)
def test_invalid_command_raises_se(self) -> None: def test_invalid_command_raises_soft_error(self) -> None:
self.assert_se("Error: Invalid group command.", process_group_command, UserInput('group '), *self.args) self.assert_se("Error: Invalid group command.", process_group_command, UserInput('group '), *self.args)
def test_invalid_command_parameters_raises_se(self) -> None: def test_invalid_command_parameters_raises_soft_error(self) -> None:
self.assert_se("Error: Invalid group command.", process_group_command, UserInput('group bad'), *self.args) self.assert_se("Error: Invalid group command.", process_group_command, UserInput('group bad'), *self.args)
def test_missing_group_id_raises_se(self) -> None: def test_missing_group_id_raises_soft_error(self) -> None:
self.assert_se("Error: No group ID specified.", process_group_command, UserInput('group join '), *self.args) self.assert_se("Error: No group ID specified.", process_group_command, UserInput('group join '), *self.args)
def test_invalid_group_id_raises_se(self) -> None: def test_invalid_group_id_raises_soft_error(self) -> None:
self.assert_se("Error: Invalid group ID.", process_group_command, UserInput('group join invalid'), *self.args) self.assert_se("Error: Invalid group ID.", process_group_command, UserInput('group join invalid'), *self.args)
def test_missing_name_raises_se(self) -> None: def test_missing_name_raises_soft_error(self) -> None:
self.assert_se("Error: No group name specified.", process_group_command, UserInput('group create '), *self.args) self.assert_se("Error: No group name specified.", process_group_command, UserInput('group create '), *self.args)
@mock.patch('builtins.input', return_value='Yes') @mock.patch('builtins.input', return_value='Yes')
@ -105,7 +105,7 @@ class TestGroupCreate(TFCTestCase):
self.group.members = self.contact_list.contacts self.group.members = self.contact_list.contacts
self.account_list = [nick_to_pub_key(str(n)) for n in range(no_contacts)] self.account_list = [nick_to_pub_key(str(n)) for n in range(no_contacts)]
def test_invalid_group_name_raises_se(self) -> None: def test_invalid_group_name_raises_soft_error(self) -> None:
# Setup # Setup
self.configure_groups(no_contacts=21) self.configure_groups(no_contacts=21)
@ -113,7 +113,7 @@ class TestGroupCreate(TFCTestCase):
self.assert_se("Error: Group name must be printable.", self.assert_se("Error: Group name must be printable.",
group_create, 'test_group\x1f', self.account_list, *self.args) group_create, 'test_group\x1f', self.account_list, *self.args)
def test_too_many_purp_accounts_raises_se(self) -> None: def test_too_many_purp_accounts_raises_soft_error(self) -> None:
# Setup # Setup
self.configure_groups(no_contacts=60) self.configure_groups(no_contacts=60)
@ -123,7 +123,7 @@ class TestGroupCreate(TFCTestCase):
group_create, 'test_group_50', cl_str, group_create, 'test_group_50', cl_str,
self.contact_list, self.group_list, self.settings, self.queues, self.master_key) self.contact_list, self.group_list, self.settings, self.queues, self.master_key)
def test_full_group_list_raises_se(self) -> None: def test_full_group_list_raises_soft_error(self) -> None:
# Setup # Setup
self.group_list = GroupList(groups=[f"testgroup_{n}" for n in range(50)]) self.group_list = GroupList(groups=[f"testgroup_{n}" for n in range(50)])
@ -180,7 +180,7 @@ class TestGroupAddMember(TFCTestCase):
def test_raises_fr_if_specified_group_does_not_exist_and_user_chooses_no(self, *_: Any) -> None: def test_raises_fr_if_specified_group_does_not_exist_and_user_chooses_no(self, *_: Any) -> None:
self.assert_se("Group creation aborted.", group_add_member, 'test_group', [], *self.args) self.assert_se("Group creation aborted.", group_add_member, 'test_group', [], *self.args)
def test_too_large_final_member_list_raises_se(self) -> None: def test_too_large_final_member_list_raises_soft_error(self) -> None:
# Setup # Setup
contact_list = ContactList(nicks=[str(n) for n in range(51)]) contact_list = ContactList(nicks=[str(n) for n in range(51)])
group_list = GroupList(groups=['testgroup']) group_list = GroupList(groups=['testgroup'])
@ -266,18 +266,18 @@ class TestGroupRmGroup(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', return_value='No') @mock.patch('builtins.input', return_value='No')
def test_cancel_of_remove_raises_se(self, *_: Any) -> None: def test_cancel_of_remove_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Group removal aborted.", group_rm_group, 'test_group', *self.args) self.assert_se("Group removal aborted.", group_rm_group, 'test_group', *self.args)
@mock.patch('builtins.input', return_value='Yes') @mock.patch('builtins.input', return_value='Yes')
def test_remove_group_not_on_transmitter_raises_se(self, _: Any) -> None: def test_remove_group_not_on_transmitter_raises_soft_error(self, _: Any) -> None:
unknown_group_id = b58encode(bytes(GROUP_ID_LENGTH)) unknown_group_id = b58encode(bytes(GROUP_ID_LENGTH))
self.assert_se("Transmitter has no group '2dVseX46KS9Sp' to remove.", self.assert_se("Transmitter has no group '2dVseX46KS9Sp' to remove.",
group_rm_group, unknown_group_id, *self.args) group_rm_group, unknown_group_id, *self.args)
self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 2) self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 2)
@mock.patch('builtins.input', return_value='Yes') @mock.patch('builtins.input', return_value='Yes')
def test_invalid_group_id_raises_se(self, _: Any) -> None: def test_invalid_group_id_raises_soft_error(self, _: Any) -> None:
invalid_group_id = b58encode(bytes(GROUP_ID_LENGTH))[:-1] invalid_group_id = b58encode(bytes(GROUP_ID_LENGTH))[:-1]
self.assert_se("Error: Invalid group name/ID.", group_rm_group, invalid_group_id, *self.args) self.assert_se("Error: Invalid group name/ID.", group_rm_group, invalid_group_id, *self.args)
@ -304,14 +304,14 @@ class TestGroupRename(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
tear_queues(self.queues) tear_queues(self.queues)
def test_contact_window_raises_se(self) -> None: def test_contact_window_raises_soft_error(self) -> None:
# Setup # Setup
self.window.type = WIN_TYPE_CONTACT self.window.type = WIN_TYPE_CONTACT
# Test # Test
self.assert_se("Error: Selected window is not a group window.", group_rename, "window", *self.args) self.assert_se("Error: Selected window is not a group window.", group_rename, "window", *self.args)
def test_invalid_group_name_raises_se(self) -> None: def test_invalid_group_name_raises_soft_error(self) -> None:
# Setup # Setup
self.window.type = WIN_TYPE_GROUP self.window.type = WIN_TYPE_GROUP
self.window.group = self.group_list.get_group('test_group') self.window.group = self.group_list.get_group('test_group')

View File

@ -30,13 +30,13 @@ from src.common.statics import (COMMAND_PACKET_QUEUE, CONFIRM_CODE_LENGTH, FINGE
KEY_MANAGEMENT_QUEUE, LOCAL_ID, LOG_SETTING_QUEUE, RELAY_PACKET_QUEUE, KEY_MANAGEMENT_QUEUE, LOCAL_ID, LOG_SETTING_QUEUE, RELAY_PACKET_QUEUE,
TM_COMMAND_PACKET_QUEUE, WIN_TYPE_CONTACT, WIN_TYPE_GROUP) TM_COMMAND_PACKET_QUEUE, WIN_TYPE_CONTACT, WIN_TYPE_GROUP)
from src.transmitter.contact import add_new_contact, change_nick, contact_setting, get_onion_address_from_user from src.transmitter.contact import (add_new_contact, change_nick, contact_setting, get_onion_address_from_user,
from src.transmitter.contact import remove_contact remove_contact)
from tests.mock_classes import ContactList, create_contact, create_group, Group, GroupList, MasterKey, OnionService from tests.mock_classes import (ContactList, create_contact, create_group, Group, GroupList, MasterKey, OnionService,
from tests.mock_classes import Settings, TxWindow, UserInput Settings, TxWindow, UserInput)
from tests.utils import cd_unit_test, cleanup, gen_queue_dict, group_name_to_group_id, ignored from tests.utils import (cd_unit_test, cleanup, gen_queue_dict, group_name_to_group_id, ignored,
from tests.utils import nick_to_onion_address, nick_to_pub_key, tear_queues, TFCTestCase, VALID_ECDHE_PUB_KEY nick_to_onion_address, nick_to_pub_key, tear_queues, TFCTestCase, VALID_ECDHE_PUB_KEY)
class TestAddNewContact(TFCTestCase): class TestAddNewContact(TFCTestCase):
@ -56,14 +56,14 @@ class TestAddNewContact(TFCTestCase):
os.remove(f'v4dkh.psk - Give to hpcra') os.remove(f'v4dkh.psk - Give to hpcra')
tear_queues(self.queues) tear_queues(self.queues)
def test_adding_new_contact_during_traffic_masking_raises_se(self) -> None: def test_adding_new_contact_during_traffic_masking_raises_soft_error(self) -> None:
# Setup # Setup
self.settings.traffic_masking = True self.settings.traffic_masking = True
# Test # Test
self.assert_se("Error: Command is disabled during traffic masking.", add_new_contact, *self.args) self.assert_se("Error: Command is disabled during traffic masking.", add_new_contact, *self.args)
def test_contact_list_full_raises_se(self) -> None: def test_contact_list_full_raises_soft_error(self) -> None:
# Setup # Setup
contact_list = ContactList(nicks=[str(n) for n in range(50)]) contact_list = ContactList(nicks=[str(n) for n in range(50)])
self.contact_list.contacts = contact_list.contacts self.contact_list.contacts = contact_list.contacts
@ -96,7 +96,7 @@ class TestAddNewContact(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=KeyboardInterrupt) @mock.patch('builtins.input', side_effect=KeyboardInterrupt)
def test_keyboard_interrupt_raises_se(self, *_: Any) -> None: def test_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None:
self.assert_se('Contact creation aborted.', add_new_contact, *self.args) self.assert_se('Contact creation aborted.', add_new_contact, *self.args)
@ -147,7 +147,7 @@ class TestRemoveContact(TFCTestCase):
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
tear_queues(self.queues) tear_queues(self.queues)
def test_contact_removal_during_traffic_masking_raises_se(self) -> None: def test_contact_removal_during_traffic_masking_raises_soft_error(self) -> None:
# Setup # Setup
self.settings.traffic_masking = True self.settings.traffic_masking = True
@ -155,13 +155,13 @@ class TestRemoveContact(TFCTestCase):
self.assert_se("Error: Command is disabled during traffic masking.", self.assert_se("Error: Command is disabled during traffic masking.",
remove_contact, UserInput(), None, *self.args) remove_contact, UserInput(), None, *self.args)
def test_missing_account_raises_se(self) -> None: def test_missing_account_raises_soft_error(self) -> None:
self.assert_se("Error: No account specified.", remove_contact, UserInput('rm '), None, *self.args) self.assert_se("Error: No account specified.", remove_contact, UserInput('rm '), None, *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('shutil.get_terminal_size', return_value=[150, 150]) @mock.patch('shutil.get_terminal_size', return_value=[150, 150])
@mock.patch('builtins.input', return_value='Yes') @mock.patch('builtins.input', return_value='Yes')
def test_invalid_account_raises_se(self, *_: Any) -> None: def test_invalid_account_raises_soft_error(self, *_: Any) -> None:
# Setup # Setup
user_input = UserInput(f'rm {nick_to_onion_address("Alice")[:-1]}') user_input = UserInput(f'rm {nick_to_onion_address("Alice")[:-1]}')
window = TxWindow(window_contacts=[self.contact_list.get_contact_by_address_or_nick('Alice')], window = TxWindow(window_contacts=[self.contact_list.get_contact_by_address_or_nick('Alice')],
@ -174,7 +174,7 @@ class TestRemoveContact(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('shutil.get_terminal_size', return_value=[150, 150]) @mock.patch('shutil.get_terminal_size', return_value=[150, 150])
@mock.patch('builtins.input', return_value='No') @mock.patch('builtins.input', return_value='No')
def test_user_abort_raises_se(self, *_: Any) -> None: def test_user_abort_raises_soft_error(self, *_: Any) -> None:
# Setup # Setup
user_input = UserInput(f'rm {nick_to_onion_address("Alice")}') user_input = UserInput(f'rm {nick_to_onion_address("Alice")}')
@ -264,11 +264,11 @@ class TestChangeNick(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
tear_queues(self.queues) tear_queues(self.queues)
def test_missing_nick_raises_se(self) -> None: def test_missing_nick_raises_soft_error(self) -> None:
self.assert_se("Error: No nick specified.", self.assert_se("Error: No nick specified.",
change_nick, UserInput("nick "), TxWindow(type=WIN_TYPE_CONTACT), *self.args) change_nick, UserInput("nick "), TxWindow(type=WIN_TYPE_CONTACT), *self.args)
def test_invalid_nick_raises_se(self) -> None: def test_invalid_nick_raises_soft_error(self) -> None:
# Setup # Setup
window = TxWindow(type=WIN_TYPE_CONTACT, window = TxWindow(type=WIN_TYPE_CONTACT,
contact=create_contact('Bob')) contact=create_contact('Bob'))
@ -277,7 +277,7 @@ class TestChangeNick(TFCTestCase):
self.assert_se("Error: Nick must be printable.", self.assert_se("Error: Nick must be printable.",
change_nick, UserInput("nick Alice\x01"), window, *self.args) change_nick, UserInput("nick Alice\x01"), window, *self.args)
def test_no_contact_raises_se(self) -> None: def test_no_contact_raises_soft_error(self) -> None:
# Setup # Setup
window = TxWindow(type=WIN_TYPE_CONTACT, window = TxWindow(type=WIN_TYPE_CONTACT,
contact=create_contact('Bob')) contact=create_contact('Bob'))
@ -327,13 +327,13 @@ class TestContactSetting(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
tear_queues(self.queues) tear_queues(self.queues)
def test_invalid_command_raises_se(self) -> None: def test_invalid_command_raises_soft_error(self) -> None:
self.assert_se("Error: Invalid command.", contact_setting, UserInput('loging on'), None, *self.args) self.assert_se("Error: Invalid command.", contact_setting, UserInput('loging on'), None, *self.args)
def test_missing_parameter_raises_se(self) -> None: def test_missing_parameter_raises_soft_error(self) -> None:
self.assert_se("Error: Invalid command.", contact_setting, UserInput(''), None, *self.args) self.assert_se("Error: Invalid command.", contact_setting, UserInput(''), None, *self.args)
def test_invalid_extra_parameter_raises_se(self) -> None: def test_invalid_extra_parameter_raises_soft_error(self) -> None:
self.assert_se("Error: Invalid command.", contact_setting, UserInput('logging on al'), None, *self.args) self.assert_se("Error: Invalid command.", contact_setting, UserInput('logging on al'), None, *self.args)
def test_enable_logging_for_user(self) -> None: def test_enable_logging_for_user(self) -> None:

View File

@ -41,10 +41,10 @@ class TestFile(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
def test_missing_file_raises_se(self) -> None: def test_missing_file_raises_soft_error(self) -> None:
self.assert_se("Error: File not found.", File, './testfile.txt', *self.args) self.assert_se("Error: File not found.", File, './testfile.txt', *self.args)
def test_empty_file_raises_se(self) -> None: def test_empty_file_raises_soft_error(self) -> None:
# Setup # Setup
with open('testfile.txt', 'wb+') as f: with open('testfile.txt', 'wb+') as f:
f.write(b'') f.write(b'')
@ -52,7 +52,7 @@ class TestFile(TFCTestCase):
# Test # Test
self.assert_se("Error: Target file is empty.", File, './testfile.txt', *self.args) self.assert_se("Error: Target file is empty.", File, './testfile.txt', *self.args)
def test_oversize_filename_raises_se(self) -> None: def test_oversize_filename_raises_soft_error(self) -> None:
# Setup # Setup
f_name = 250 * 'a' + '.txt' f_name = 250 * 'a' + '.txt'
with open(f_name, 'wb+') as f: with open(f_name, 'wb+') as f:

View File

@ -33,12 +33,12 @@ from src.common.statics import (COMMAND_PACKET_QUEUE, CONFIRM_CODE_LENGTH, ECDH
LOCAL_PUBKEY, RELAY_PACKET_QUEUE, SYMMETRIC_KEY_LENGTH, TFC_PUBLIC_KEY_LENGTH, LOCAL_PUBKEY, RELAY_PACKET_QUEUE, SYMMETRIC_KEY_LENGTH, TFC_PUBLIC_KEY_LENGTH,
WIN_TYPE_CONTACT, WIN_TYPE_GROUP, XCHACHA20_NONCE_LENGTH) WIN_TYPE_CONTACT, WIN_TYPE_GROUP, XCHACHA20_NONCE_LENGTH)
from src.transmitter.key_exchanges import create_pre_shared_key, export_onion_service_data, new_local_key from src.transmitter.key_exchanges import (create_pre_shared_key, export_onion_service_data, new_local_key,
from src.transmitter.key_exchanges import rxp_load_psk, start_key_exchange, verify_fingerprints rxp_load_psk, start_key_exchange, verify_fingerprints)
from tests.mock_classes import ContactList, create_contact, Gateway, OnionService, Settings, TxWindow from tests.mock_classes import ContactList, create_contact, Gateway, OnionService, Settings, TxWindow
from tests.utils import cd_unit_test, cleanup, gen_queue_dict, ignored, nick_to_pub_key from tests.utils import (cd_unit_test, cleanup, gen_queue_dict, ignored, nick_to_pub_key, nick_to_short_address,
from tests.utils import nick_to_short_address, tear_queues, TFCTestCase, VALID_ECDHE_PUB_KEY tear_queues, TFCTestCase, VALID_ECDHE_PUB_KEY)
class TestOnionService(TFCTestCase): class TestOnionService(TFCTestCase):
@ -72,7 +72,7 @@ class TestLocalKey(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
tear_queues(self.queues) tear_queues(self.queues)
def test_new_local_key_when_traffic_masking_is_enabled_raises_se(self) -> None: def test_new_local_key_when_traffic_masking_is_enabled_raises_soft_error(self) -> None:
self.settings.traffic_masking = True self.settings.traffic_masking = True
self.contact_list.contacts = [create_contact(LOCAL_ID)] self.contact_list.contacts = [create_contact(LOCAL_ID)]
self.assert_se("Error: Command is disabled during traffic masking.", new_local_key, *self.args) self.assert_se("Error: Command is disabled during traffic masking.", new_local_key, *self.args)
@ -117,7 +117,7 @@ class TestLocalKey(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=KeyboardInterrupt) @mock.patch('builtins.input', side_effect=KeyboardInterrupt)
@mock.patch('os.getrandom', lambda x, flags: x * b'a') @mock.patch('os.getrandom', lambda x, flags: x * b'a')
def test_keyboard_interrupt_raises_se(self, *_: Any) -> None: def test_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Local key setup aborted.", new_local_key, *self.args) self.assert_se("Local key setup aborted.", new_local_key, *self.args)
@ -147,12 +147,12 @@ class TestKeyExchange(TFCTestCase):
@mock.patch('shutil.get_terminal_size', return_value=[200, 200]) @mock.patch('shutil.get_terminal_size', return_value=[200, 200])
@mock.patch('builtins.input', return_value=b58encode(bytes(TFC_PUBLIC_KEY_LENGTH), public_key=True)) @mock.patch('builtins.input', return_value=b58encode(bytes(TFC_PUBLIC_KEY_LENGTH), public_key=True))
def test_zero_public_key_raises_se(self, *_: Any) -> None: def test_zero_public_key_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Error: Zero public key", start_key_exchange, nick_to_pub_key("Alice"), 'Alice', *self.args) self.assert_se("Error: Zero public key", start_key_exchange, nick_to_pub_key("Alice"), 'Alice', *self.args)
@mock.patch('shutil.get_terminal_size', return_value=[200, 200]) @mock.patch('shutil.get_terminal_size', return_value=[200, 200])
@mock.patch('builtins.input', return_value=b58encode((TFC_PUBLIC_KEY_LENGTH-1)*b'a', public_key=True)) @mock.patch('builtins.input', return_value=b58encode((TFC_PUBLIC_KEY_LENGTH-1)*b'a', public_key=True))
def test_invalid_public_key_length_raises_se(self, *_: Any) -> None: def test_invalid_public_key_length_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Error: Invalid public key length", self.assert_se("Error: Invalid public key length",
start_key_exchange, nick_to_pub_key("Alice"), 'Alice', *self.args) start_key_exchange, nick_to_pub_key("Alice"), 'Alice', *self.args)
@ -164,7 +164,7 @@ class TestKeyExchange(TFCTestCase):
'No']) # Fingerprint mismatch) 'No']) # Fingerprint mismatch)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('shutil.get_terminal_size', return_value=[200, 200]) @mock.patch('shutil.get_terminal_size', return_value=[200, 200])
def test_fingerprint_mismatch_raises_se(self, *_: Any) -> None: def test_fingerprint_mismatch_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Error: Fingerprint mismatch", start_key_exchange, nick_to_pub_key("Alice"), 'Alice', *self.args) self.assert_se("Error: Fingerprint mismatch", start_key_exchange, nick_to_pub_key("Alice"), 'Alice', *self.args)
@mock.patch('builtins.input', side_effect=['', # Resend public key @mock.patch('builtins.input', side_effect=['', # Resend public key
@ -297,7 +297,7 @@ class TestPSK(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('getpass.getpass', side_effect=KeyboardInterrupt) @mock.patch('getpass.getpass', side_effect=KeyboardInterrupt)
def test_keyboard_interrupt_raises_se(self, *_: Any) -> None: def test_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None:
self.assert_se("PSK generation aborted.", create_pre_shared_key, nick_to_pub_key("Alice"), 'Alice', *self.args) self.assert_se("PSK generation aborted.", create_pre_shared_key, nick_to_pub_key("Alice"), 'Alice', *self.args)
@ -320,14 +320,14 @@ class TestReceiverLoadPSK(TFCTestCase):
# Test # Test
self.assert_se("Error: Command is disabled during traffic masking.", rxp_load_psk, None, None, *self.args) self.assert_se("Error: Command is disabled during traffic masking.", rxp_load_psk, None, None, *self.args)
def test_active_group_raises_se(self) -> None: def test_active_group_raises_soft_error(self) -> None:
# Setup # Setup
window = TxWindow(type=WIN_TYPE_GROUP) window = TxWindow(type=WIN_TYPE_GROUP)
# Test # Test
self.assert_se("Error: Group is selected.", rxp_load_psk, window, None, *self.args) self.assert_se("Error: Group is selected.", rxp_load_psk, window, None, *self.args)
def test_ecdhe_key_raises_se(self) -> None: def test_ecdhe_key_raises_soft_error(self) -> None:
# Setup # Setup
contact = create_contact('Alice') contact = create_contact('Alice')
contact_list = ContactList(contacts=[contact]) contact_list = ContactList(contacts=[contact])
@ -360,7 +360,7 @@ class TestReceiverLoadPSK(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=KeyboardInterrupt) @mock.patch('builtins.input', side_effect=KeyboardInterrupt)
def test_keyboard_interrupt_raises_se(self, *_: Any) -> None: def test_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None:
# Setup # Setup
contact = create_contact('Alice', kex_status=KEX_STATUS_NO_RX_PSK) contact = create_contact('Alice', kex_status=KEX_STATUS_NO_RX_PSK)
contact_list = ContactList(contacts=[contact]) contact_list = ContactList(contacts=[contact])
@ -369,7 +369,7 @@ class TestReceiverLoadPSK(TFCTestCase):
contact=contact) contact=contact)
# Test # Test
self.assert_se("PSK verification aborted.", rxp_load_psk, window, contact_list, *self.args) self.assert_se("PSK install verification aborted.", rxp_load_psk, window, contact_list, *self.args)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -35,11 +35,11 @@ from src.common.statics import (ASSEMBLY_PACKET_LENGTH, COMMAND, COMMAND_PACKET_
TM_COMMAND_PACKET_QUEUE, TM_FILE_PACKET_QUEUE, TM_MESSAGE_PACKET_QUEUE, TM_COMMAND_PACKET_QUEUE, TM_FILE_PACKET_QUEUE, TM_MESSAGE_PACKET_QUEUE,
WIN_TYPE_CONTACT, WIN_TYPE_GROUP) WIN_TYPE_CONTACT, WIN_TYPE_GROUP)
from src.transmitter.packet import cancel_packet, queue_command, queue_file, queue_message, queue_assembly_packets from src.transmitter.packet import (cancel_packet, queue_command, queue_file, queue_message, queue_assembly_packets,
from src.transmitter.packet import send_file, send_packet, split_to_assembly_packets send_file, send_packet, split_to_assembly_packets)
from tests.mock_classes import create_contact, create_group, create_keyset, Gateway, ContactList, KeyList from tests.mock_classes import (create_contact, create_group, create_keyset, Gateway, ContactList, KeyList,
from tests.mock_classes import nick_to_pub_key, OnionService, Settings, TxWindow, UserInput nick_to_pub_key, OnionService, Settings, TxWindow, UserInput)
from tests.utils import cd_unit_test, cleanup, gen_queue_dict, tear_queue, tear_queues, TFCTestCase from tests.utils import cd_unit_test, cleanup, gen_queue_dict, tear_queue, tear_queues, TFCTestCase
@ -106,14 +106,14 @@ class TestSendFile(TFCTestCase):
cleanup(self.unit_test_dir) cleanup(self.unit_test_dir)
tear_queues(self.queues) tear_queues(self.queues)
def test_traffic_masking_raises_se(self) -> None: def test_traffic_masking_raises_soft_error(self) -> None:
self.settings.traffic_masking = True self.settings.traffic_masking = True
self.assert_se("Error: Command is disabled during traffic masking.", send_file, "testfile.txt", *self.args) self.assert_se("Error: Command is disabled during traffic masking.", send_file, "testfile.txt", *self.args)
def test_missing_file_raises_se(self) -> None: def test_missing_file_raises_soft_error(self) -> None:
self.assert_se("Error: File not found.", send_file, "testfile.txt", *self.args) self.assert_se("Error: File not found.", send_file, "testfile.txt", *self.args)
def test_empty_file_raises_se(self) -> None: def test_empty_file_raises_soft_error(self) -> None:
# Setup # Setup
open('testfile.txt', 'wb+').close() open('testfile.txt', 'wb+').close()
@ -170,7 +170,7 @@ class TestQueueFile(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=file_list) @mock.patch('builtins.input', side_effect=file_list)
def test_tfc_database_raises_se(self, *_: Any) -> None: def test_tfc_database_raises_soft_error(self, *_: Any) -> None:
window = TxWindow(name='Alice', window = TxWindow(name='Alice',
type=WIN_TYPE_CONTACT, type=WIN_TYPE_CONTACT,
type_print='contact', type_print='contact',
@ -253,7 +253,7 @@ class TestQueueFile(TFCTestCase):
@mock.patch('shutil.get_terminal_size', return_value=[150, 150]) @mock.patch('shutil.get_terminal_size', return_value=[150, 150])
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=['./testfile.txt', KeyboardInterrupt]) @mock.patch('builtins.input', side_effect=['./testfile.txt', KeyboardInterrupt])
def test_keyboard_interrupt_raises_se(self, *_: Any) -> None: def test_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None:
# Setup # Setup
input_data = os.urandom(2000) input_data = os.urandom(2000)
with open('testfile.txt', 'wb+') as f: with open('testfile.txt', 'wb+') as f:
@ -344,7 +344,7 @@ class TestQueueAssemblyPackets(unittest.TestCase):
log_messages=True) log_messages=True)
self.window.window_contacts = [create_contact('Alice')] self.window.window_contacts = [create_contact('Alice')]
self.args = self.settings, self.queues, self.window self.args = self.settings, self.queues, self.window
def tearDown(self) -> None: def tearDown(self) -> None:
"""Post-test actions.""" """Post-test actions."""
tear_queues(self.queues) tear_queues(self.queues)

View File

@ -24,9 +24,11 @@ import unittest
from unittest import mock from unittest import mock
from typing import Any from typing import Any
from src.common.statics import COMMAND, FILE, MESSAGE, WIN_TYPE_CONTACT, WIN_TYPE_GROUP from src.common.statics import COMMAND, FILE, MESSAGE, WIN_TYPE_CONTACT, WIN_TYPE_GROUP
from src.transmitter.user_input import get_input, process_aliases, UserInput from src.transmitter.user_input import get_input, process_aliases, UserInput
from tests.mock_classes import create_contact, create_group, Settings, TxWindow
from tests.mock_classes import create_contact, create_group, Settings, TxWindow
class TestProcessAliases(unittest.TestCase): class TestProcessAliases(unittest.TestCase):

View File

@ -33,8 +33,8 @@ from src.transmitter.windows import select_window, TxWindow
from tests.mock_classes import ContactList, create_contact, Gateway, GroupList, OnionService, Settings, UserInput from tests.mock_classes import ContactList, create_contact, Gateway, GroupList, OnionService, Settings, UserInput
from tests.utils import gen_queue_dict, group_name_to_group_id, nick_to_onion_address, nick_to_pub_key from tests.utils import (gen_queue_dict, group_name_to_group_id, nick_to_onion_address, nick_to_pub_key,
from tests.utils import tear_queues, TFCTestCase, VALID_ECDHE_PUB_KEY tear_queues, TFCTestCase, VALID_ECDHE_PUB_KEY)
class TestTxWindow(TFCTestCase): class TestTxWindow(TFCTestCase):
@ -72,7 +72,7 @@ class TestTxWindow(TFCTestCase):
# Test # Test
self.assertEqual(len(self.window), 2) self.assertEqual(len(self.window), 2)
def test_group_window_change_during_traffic_masking_raises_se(self) -> None: def test_group_window_change_during_traffic_masking_raises_soft_error(self) -> None:
# Setup # Setup
self.settings.traffic_masking = True self.settings.traffic_masking = True
self.window.uid = 'test_group' self.window.uid = 'test_group'
@ -81,7 +81,7 @@ class TestTxWindow(TFCTestCase):
self.assert_se("Error: Can't change window during traffic masking.", self.assert_se("Error: Can't change window during traffic masking.",
self.window.select_tx_window, *self.args, selection='test_group_2', cmd=True) self.window.select_tx_window, *self.args, selection='test_group_2', cmd=True)
def test_contact_window_change_during_traffic_masking_raises_se(self) -> None: def test_contact_window_change_during_traffic_masking_raises_soft_error(self) -> None:
# Setup # Setup
self.settings.traffic_masking = True self.settings.traffic_masking = True
self.window.uid = nick_to_pub_key("Alice") self.window.uid = nick_to_pub_key("Alice")
@ -109,7 +109,7 @@ class TestTxWindow(TFCTestCase):
self.assertIsNone(self.window.select_tx_window(*self.args, selection='test_group', cmd=True)) self.assertIsNone(self.window.select_tx_window(*self.args, selection='test_group', cmd=True))
self.assertEqual(self.window.uid, group_name_to_group_id('test_group')) self.assertEqual(self.window.uid, group_name_to_group_id('test_group'))
def test_invalid_selection_raises_se(self) -> None: def test_invalid_selection_raises_soft_error(self) -> None:
# Setup # Setup
self.window.uid = nick_to_pub_key("Alice") self.window.uid = nick_to_pub_key("Alice")
@ -275,12 +275,12 @@ class TestTxWindow(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=['/rm ']) @mock.patch('builtins.input', side_effect=['/rm '])
def test_missing_account_when_removing_raises_se(self, *_: Any) -> None: def test_missing_account_when_removing_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Error: No account specified.", self.window.select_tx_window, *self.args) self.assert_se("Error: No account specified.", self.window.select_tx_window, *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=['/rm Charlie', 'yes']) @mock.patch('builtins.input', side_effect=['/rm Charlie', 'yes'])
def test_unknown_account_when_removing_raises_se(self, *_: Any) -> None: def test_unknown_account_when_removing_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Error: Unknown contact 'Charlie'.", self.window.select_tx_window, *self.args) self.assert_se("Error: Unknown contact 'Charlie'.", self.window.select_tx_window, *self.args)
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@ -301,7 +301,7 @@ class TestTxWindow(TFCTestCase):
@mock.patch('time.sleep', return_value=None) @mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=['/help']) @mock.patch('builtins.input', side_effect=['/help'])
def test_invalid_command_raises_se(self, *_: Any) -> None: def test_invalid_command_raises_soft_error(self, *_: Any) -> None:
self.assert_se("Error: Invalid command.", self.window.select_tx_window, *self.args) self.assert_se("Error: Invalid command.", self.window.select_tx_window, *self.args)
@ -323,7 +323,7 @@ class TestSelectWindow(TFCTestCase):
"""Post-test actions.""" """Post-test actions."""
tear_queues(self.queues) tear_queues(self.queues)
def test_invalid_selection_raises_se(self) -> None: def test_invalid_selection_raises_soft_error(self) -> None:
# Setup # Setup
self.user_input.plaintext = 'msg' self.user_input.plaintext = 'msg'
self.assert_se("Error: Invalid recipient.", select_window, *self.args) self.assert_se("Error: Invalid recipient.", select_window, *self.args)

6
tfc.py
View File

@ -95,15 +95,15 @@ def main() -> None:
ensure_dir(working_dir) ensure_dir(working_dir)
os.chdir(working_dir) os.chdir(working_dir)
operation, local_test, data_diode_sockets = process_arguments() operation, local_test, data_diode_sockets, qubes = process_arguments()
check_kernel_version() check_kernel_version()
print_title(operation) print_title(operation)
master_key = MasterKey( operation, local_test) master_key = MasterKey( operation, local_test)
gateway = Gateway( operation, local_test, data_diode_sockets) gateway = Gateway( operation, local_test, data_diode_sockets, qubes)
settings = Settings( master_key, operation, local_test) settings = Settings( master_key, operation, local_test, qubes)
contact_list = ContactList(master_key, settings) contact_list = ContactList(master_key, settings)
key_list = KeyList( master_key, settings) key_list = KeyList( master_key, settings)
group_list = GroupList( master_key, settings, contact_list) group_list = GroupList( master_key, settings, contact_list)

View File

@ -1,6 +1,6 @@
--- ---
- apparmor-profiles: - apparmor-profiles:
- '/opt/tfc/venv_relay/bin/python3.7' - '/usr/bin/python3.7'
users: users:
- 'amnesia' - 'amnesia'
commands: commands:

View File

@ -40,9 +40,14 @@ sudo rm -f /usr/share/pixmaps/tfc.png
sudo rm -f /usr/share/applications/TFC-Dev.desktop sudo rm -f /usr/share/applications/TFC-Dev.desktop
sudo rm -f /usr/share/applications/TFC-Local-test.desktop sudo rm -f /usr/share/applications/TFC-Local-test.desktop
sudo rm -f /usr/share/applications/TFC-RP.desktop sudo rm -f /usr/share/applications/TFC-RP.desktop
sudo rm -f /usr/share/applications/TFC-RP-Qubes.desktop
sudo rm -f /usr/share/applications/TFC-RP-Tails.desktop sudo rm -f /usr/share/applications/TFC-RP-Tails.desktop
sudo rm -f /usr/share/applications/TFC-RxP.desktop sudo rm -f /usr/share/applications/TFC-RxP-Qubes.desktop
sudo rm -f /usr/share/applications/TFC-TxP.desktop sudo rm -f /usr/share/applications/TFC-TxP.desktop
sudo rm -f /usr/share/applications/TFC-TxP-Qubes.desktop
sudo rm -f /usr/bin/tfc-transmitter
sudo rm -f /usr/bin/tfc-receiver
sudo rm -f /usr/bin/tfc-relay
sudo rm -rf /opt/tfc/ sudo rm -rf /opt/tfc/
yn_prompt "Remove user data?" "rm -rf $HOME/tfc/" yn_prompt "Remove user data?" "rm -rf $HOME/tfc/"