This commit is contained in:
maqp 2019-10-23 07:21:29 +03:00
parent da105fe2bb
commit eac6b54c65
108 changed files with 9419 additions and 696 deletions

View File

@ -16,8 +16,7 @@ before_install:
install:
- pip install pytest pytest-cov pyyaml coveralls
- pip install -r requirements.txt --require-hashes
- pip install -r requirements-relay.txt --require-hashes
- pip install -r requirements-dev.txt
script:
- py.test --cov=src tests/

View File

@ -83,9 +83,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- The Argon2 library, Copyright © 2015, Hynek Schlawack
(https://github.com/hynek/argon2_cffi)
- The asn1crypto library, Copyright © 2015-2018, Will Bond <will@wbond.net>
(https://github.com/wbond/asn1crypto)
- The src.common.encoding Base58 implementation, Copyright © 2015, David Keijser
(https://github.com/keis/base58)
@ -531,6 +528,11 @@ Public License instead of this License.
- The src.relay.onion Tor class, Copyright © 2014-2019, Micah Lee <micah@micahflee.com>
(https://github.com/micahflee/onionshare)
- gnome-terminal, Copyright © Guilherme de S. Pastore <gpastore@gnome.org>,
Havoc Pennington <hp@redhat.com>,
Mariano Suárez-Alvarez <mariano@gnome.org>
(https://gitlab.gnome.org/GNOME/gnome-terminal)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Preamble

View File

@ -1,4 +1,4 @@
<img align="right" src="https://cs.helsinki.fi/u/oottela/tfclogo.png" style="position: relative; top: 0; left: 0;">
<img align="right" src="https://cs.helsinki.fi/u/oottela/tfc_logo.png" style="position: relative; top: 0; left: 0;">
### Tinfoil Chat
@ -67,8 +67,8 @@ and schedule of communication, even if the Networked Computer is compromised.
### How it works
![](https://www.cs.helsinki.fi/u/oottela/wiki/readme/how_it_works.png)
[System overview](https://www.cs.helsinki.fi/u/oottela/wiki/readme/how_it_works.png)
![](https://www.cs.helsinki.fi/u/oottela/wiki/readme/how_it_works2.png)
[System overview](https://www.cs.helsinki.fi/u/oottela/wiki/readme/how_it_works2.png)
TFC uses three computers per endpoint: Source Computer, Networked Computer, and
Destination Computer.
@ -114,8 +114,8 @@ the user.
3. The Networked Computer is assumed to be compromised. All sensitive data that
passes through it is encrypted and signed with no exceptions.
![](https://www.cs.helsinki.fi/u/oottela/wiki/readme/attacks.png)
[Exfiltration security](https://www.cs.helsinki.fi/u/oottela/wiki/readme/attacks.png)
![](https://www.cs.helsinki.fi/u/oottela/wiki/readme/attacks2.png)
[Exfiltration security](https://www.cs.helsinki.fi/u/oottela/wiki/readme/attacks2.png)
#### Data diode
Optical repeater inside the
@ -131,12 +131,14 @@ fundamental laws of physics.
#### Source/Destination Computer
- Debian 10
- *buntu 19.04 (or newer)
- PureOS 9.0
- *buntu 19.10
#### Networked Computer
- Tails (Debian Buster or newer)
- Tails 4.0
- Debian 10
- *buntu 19.04 (or newer)
- PureOS 9.0
- *buntu 19.10
### More information

6
dd.py
View File

@ -29,9 +29,9 @@ from typing import Any, Dict, Tuple
from src.common.misc import get_terminal_height, get_terminal_width, ignored, monitor_processes
from src.common.output import clear_screen
from src.common.statics import DATA_FLOW, DD_ANIMATION_LENGTH, DD_OFFSET_FROM_CENTER, DST_DD_LISTEN_SOCKET
from src.common.statics import DST_LISTEN_SOCKET, EXIT_QUEUE, IDLE, LOCALHOST, NC, NCDCLR, NCDCRL, RP_LISTEN_SOCKET
from src.common.statics import SCNCLR, SCNCRL, SRC_DD_LISTEN_SOCKET
from src.common.statics import (DATA_FLOW, DD_ANIMATION_LENGTH, DD_OFFSET_FROM_CENTER, DST_DD_LISTEN_SOCKET,
DST_LISTEN_SOCKET, EXIT_QUEUE, IDLE, LOCALHOST, NC, NCDCLR, NCDCRL, RP_LISTEN_SOCKET,
SCNCLR, SCNCRL, SRC_DD_LISTEN_SOCKET)
def draw_frame(argv: str, # Arguments for the simulator position/orientation

View File

@ -16,6 +16,30 @@
# You should have received a copy of the GNU General Public License
# along with TFC. If not, see <https://www.gnu.org/licenses/>.
# PIP dependency file names
ARGON2=argon2_cffi-19.1.0-cp34-abi3-manylinux1_x86_64.whl
CERTIFI=certifi-2019.9.11-py2.py3-none-any.whl
CFFI=cffi-1.13.1-cp37-cp37m-manylinux1_x86_64.whl
CHARDET=chardet-3.0.4-py2.py3-none-any.whl
CLICK=Click-7.0-py2.py3-none-any.whl
CRYPTOGRAPHY=cryptography-2.8-cp34-abi3-manylinux1_x86_64.whl
FLASK=Flask-1.1.1-py2.py3-none-any.whl
IDNA=idna-2.8-py2.py3-none-any.whl
ITSDANGEROUS=itsdangerous-1.1.0-py2.py3-none-any.whl
JINJA2=Jinja2-2.10.3-py2.py3-none-any.whl
MARKUPSAFE=MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl
PYCPARSER=pycparser-2.19.tar.gz
PYNACL=PyNaCl-1.3.0-cp34-abi3-manylinux1_x86_64.whl
PYSERIAL=pyserial-3.4-py2.py3-none-any.whl
PYSOCKS=PySocks-1.7.1-py3-none-any.whl
REQUESTS=requests-2.22.0-py2.py3-none-any.whl
SETUPTOOLS=setuptools-41.4.0-py2.py3-none-any.whl
SIX=six-1.12.0-py2.py3-none-any.whl
STEM=stem-1.7.1.tar.gz
URLLIB3=urllib3-1.25.6-py2.py3-none-any.whl
VIRTUALENV=virtualenv-16.7.7-py2.py3-none-any.whl
WERKZEUG=Werkzeug-0.16.0-py2.py3-none-any.whl
function compare_digest {
# Compare the SHA512 digest of TFC file against the digest pinned in
@ -30,111 +54,89 @@ function compare_digest {
function verify_tcb_requirements_files {
# To minimize the time TCB installer configuration stays online, only
# the requirements files are authenticated between downloads.
compare_digest c3f27d766f2795bf0c87ddb0cec43f1f9919c2cf20db5eff62e818d67436f1520b6001bf9e7649c5508e62b221c02039b6cb29f7393ba1dbacf9a442cb3bb8b2 '' requirements.txt
compare_digest 2c9e865be4231d346504bef99159d987803944b4ed7a1f0dbb7e674d4043e83c45771da34b7c4772f25101b81f41f2bafc75bfd07e58d37ddf7d3dc1aa32da24 '' requirements-venv.txt
# To minimize the time TCB installer configuration stays online, only
# the requirements files are authenticated between downloads.
compare_digest 99912fe2f7240a9b163292ff83c28b6ab41ee1c10bf96cc57f2c066537d3f153b46280e2c769b0f273c6bc36c74badb42d3c66c6fb3d16862dc96ff27319788d '' requirements.txt
compare_digest 97558ed189976ccd54e3a25bcf639f1944aa43f4a4f42ff5ef2cf22349a7b649272e91746041b4e04b2f33adf1fab8818c339b1cc58f9353af3e5ac76cb1ec0b '' requirements-venv.txt
}
function verify_files {
# Verify the authenticity of the rest of the TFC files.
compare_digest e4f81f752001dbd04d46314ea6d8867393c3ad5ed85c2d3e336a8018913446f5855525e0ca03671ab8d26d8af1fe16416c8f5a163cad795867284a726adfeb31 '' dd.py
compare_digest bcb8a7ce1eb2d2f064b560ca5a8e467f84e3a0c3d643771e7782c792e89494600436e52c12f0a8471bf4a1da116f82ed732b8e06783534227a31f576f7adbd6c '' dd.py
compare_digest d361e5e8201481c6346ee6a886592c51265112be550d5224f1a7a6e116255c2f1ab8788df579d9b8372ed7bfd19bac4b6e70e00b472642966ab5b319b99a2686 '' LICENSE
compare_digest 651fccf97f1a1a3541078a19191b834448cb0c3256c6c2822989b572d67bc4b16932edea226ecf0cbd792fc6a11f4db841367d3ecd93efa67b27eaee0cc04cb7 '' LICENSE-3RD-PARTY
compare_digest 84a4e5b287ba4f600fc170913f5bdcd3db67c6d75a57804331a04336a9931c7ce9c58257ad874d3f197c097869438bb1d2932f06f5762c44f264617681eab287 '' relay.py
compare_digest 2865708ab24c3ceeaf0a6ec382fb7c331fdee52af55a111c1afb862a336dd757d597f91b94267da009eb74bbc77d01bf78824474fa6f0aa820cd8c62ddb72138 '' requirements-dev.txt
compare_digest 1ab71b773a0451807eda87a1442dd79529de62f617e8087a21c0f3ad77e4fe22218e05a94d19d1660b2967a9908fb722f750a64a75e650b178c818c8536f64db '' requirements-relay.txt
compare_digest 6d93d5513f66389778262031cbba95e1e38138edaec66ced278db2c2897573247d1de749cf85362ec715355c5dfa5c276c8a07a394fd5cf9b45c7a7ae6249a66 '' tfc.png
compare_digest a7b8090855295adfc22528b2f89bed88617b5e990ffe58e3a42142a9a4bea6b1b67c757c9b7d1eafeec22eddee9f9891b44afffa52d31ce5d050f08a1734874d '' tfc.py
compare_digest 7e519d20fef24e25e88ec4a9c03abadf513b084e05038f17c62ca7899c2f9174a953caa0bfbd3b61e455e243513cdab737c22a34d73ebab07b65d3ce99100f0a '' LICENSE-3RD-PARTY
compare_digest 99815d0cfbca7d83409b7317947fe940fe93fd94b50e6099a566563ee6999e33830fd883ff61e5367a040d5fda3f2a43165ef0dc6155e14a573e07dc27eba70d '' relay.py
compare_digest 28d06826a45ca4d64c2b4d06859ee7a0c7152198fe49b85681f7ce6b9c02b1a103fd7f3514b05b24e95e2ec5f48ce02529a2b4f2ea806b333e8141b1650d1257 '' requirements-dev.txt
compare_digest 8a57366899139b9906f0a75272c702575a6cd5c6ca2dd09f0dbd1be9efd5341178f9d3d64fec113af7d1fdccbb5cbdf384133aa3afa3672292e37405f60cf0a8 '' requirements-relay.txt
compare_digest 8ecd5957f3bfbe237549e8772720cba5b5899b51a475063edcbc416ad5f77f614da2c9069aeb31bca6d2bb74ce6f2877d29df178ec3ecf6d5dd05daaff51c6b2 '' requirements-relay-tails.txt
compare_digest 4a44501e21d463ff8569a1665b75c2e4d8de741d445dc3e442479cbb7282646045129233bd7313df4b9c2e64ec86b7615a8196ae2b3350de933731926d39bbda '' requirements-setuptools.txt
compare_digest 79f8272a2ab122a48c60630c965cd9d000dcafabf5ee9d69b1c33c58ec321feb17e4654dbbbf783cc8868ccdfe2777d60c6c3fc9ef16f8264d9fcf43724e83c2 '' tfc.png
compare_digest e4dadae63adcd72108fcfa04401f42a1bae956008303d09f22e849b207ebca699306f2bd4034ee96a5531028719f5e41689205ec8ef12cd1726a86376d3aec3e '' tfc.py
compare_digest 7ae1c2a393d96761843bea90edd569244bfb4e0f9943e68a4549ee46d93180d26d4101c2471c1a37785ccdfaef45eedecf15057c0a9cc6c056460c5f9a69d37b '' tfc.yml
compare_digest c6a61b3050624874cabc28cc51e947aa1ba629b0fd62564466b902cc433c08be6ae64d53bb2f33158e198c60ef2eb7c38b0bee1a64ef9659d101dee07557ddc7 '' uninstall.sh
compare_digest d4f503df2186db02641f54a545739d90974b6d9d920f76ad7e93fe1a38a68a85c167da6c19f7574d11fbb69e57d563845d174d420c55691bc2cd75a1a72806dc launchers/ terminator-config-local-test
compare_digest fed9f056541afe1822ec41bce315fc6ce94652318088ecc905ddc657a15525d740ca83ff335e5090b9e3844027a706990da72a754ffa3dcd255b21e263597c78 launchers/ TFC-Local-test.desktop
compare_digest 011a64aa7790253b008f0adfe940108dc94c08adec9b3a42efd1ac0d6405a82d828b3d416736ad64235b6b90ef2f6712fc10a70d8e97870670e5b84cf25fe54e launchers/ TFC-RP.desktop
compare_digest 12c86594b28f5dae48e4d216781034917ab80ca46f30cb3d33a0dec615d415830fdc41228f933c8fef651eb51643390fe5521e70f9efc642ea5c8b2e34442e00 launchers/ TFC-RP-Tails.desktop
compare_digest 270153a45e5426db326ba2144ca454bb57ddf8f53731c1948ee8fa33dedcb98e32e19506b0fa923e5a7f6fca9a5b67338216c19417e52289726721efe12abd98 launchers/ TFC-RxP.desktop
compare_digest 691d0cc4f03bd9c2874711ba97e347e705e4eadf53c5b14562e7e1a419ae892a9ce156b6e5a26945bde72c0b3e56ec6ca2d24fb3bf23848fb44808b7681e4141 launchers/ TFC-TxP.desktop
compare_digest a5611269e2f69a452840ae13d888bd80d6f8e5e78fdab0cb666440491d8431e6c326dc57a52df7d9e68ecd139376606c9f6c945207f2427bb21c114fe26c0af7 launchers/ TFC-Local-test.desktop
compare_digest 9263737fca4773672515e0f4708e147b634bd09c8d068966806bb77d3b38dcf60b1f933846f9a649e795760ff141a31dc2b58fad38ef2afbaedb33d2f479a29b launchers/ TFC-RP.desktop
compare_digest 9263737fca4773672515e0f4708e147b634bd09c8d068966806bb77d3b38dcf60b1f933846f9a649e795760ff141a31dc2b58fad38ef2afbaedb33d2f479a29b launchers/ TFC-RP-Tails.desktop
compare_digest 113d1f8f6bc03009ef1ccfe1aed8a90bdecb54e66bd91ed815bbd83cb695419a25c614de8287475d3beab832cfcaf6d549c06832f2ea098d29ff049d7cd91da7 launchers/ TFC-RxP.desktop
compare_digest 1f4d4e216039b63f2579eef17dc18df5e2f1e65f09e619b62adb8dceb128de6ffe5784ea0ff1dc846af21e1ce641bc612df51e37e205fee210f94dd87b86f467 launchers/ TFC-TxP.desktop
compare_digest cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e src/ __init__.py
compare_digest cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e src/common/ __init__.py
compare_digest 92c7f788c41984cf75ad879e281f73a1a334dd759b858be11f91481a0e754e2db1527c9d3ab369aad7b8cf139492d5809c0c0542e46ff27637ee5b4b655b726f src/common/ crypto.py
compare_digest 0b830ad6705f90dc8e554c8b06f31054d9222d3b35db92470eaf3f9af935aae3107b61142ea68129943e4227a45dfe26a87f23e9dd95a7794ae65c397bd35641 src/common/ db_contacts.py
compare_digest bad54588b3e713caf94aba3216090410f77f92064aecfea004a33487a21f9fffcf5d05782b91104c27ec499e38196881a76d6002ec1149816d8c53560182fba9 src/common/ db_groups.py
compare_digest 46e503af15fb7a1ea8387fa8f22674d0926eda0d879d43f99076ca967295d5e3727a411aa11c4bb4d832783c5daa9f4b7ef4491b90112c1be405910d269abaf4 src/common/ db_keys.py
compare_digest 68b2c7761623ff9e4ce596e170c8e1ce2b6129bbbbb9fc9e48a120974d30db6e471bb5d514f5dc8d7edc8e0abcb0cd0c2b4e5da739f4d98c6fa8c8e07af0d8ef src/common/ db_logs.py
compare_digest 1a3783e6ea5643bc799b9745d0a8abb82f9e814ca01da3414df86faaa007aaa21609a88e07c8947e43567a787f114db57143f10585053e92f96807858adec96a src/common/ db_masterkey.py
compare_digest 5befbe864e2b09125be2b04cdfee8d13e7616715fc20a0fa06da270e34b555602b2df825fd429059056b2beb1497c50dafdc682d59a43a483837445861647e9d src/common/ db_onion.py
compare_digest 328375464a5064829621fb8154c94cf15a1e7564122d99f03c9e578dcd6d6f264a671412b738b4971a6e505062673df40cc89f7b67c7ef2a761236c3f1247e93 src/common/ db_settings.py
compare_digest e3f760b03eb38d3ec97b04123f8cf36825d616e126bfb9fb575927586cbb1911dce448c08cb8cfb26be0635c2e6abaaad8ea94f0513672c288c78892010ab146 src/common/ encoding.py
compare_digest f7ff99c8a5f84b892b959fe391cdfe416d103a5a4d25fcd46842f768583a5b241036ee9c4b6713a938e8234f5af82a770ffe28975292d162b6d5bea69a120ad8 src/common/ exceptions.py
compare_digest 67cbb9e983aeb49d8a0cc8666aa5c1dad4b868e4fc968c72c7fc4290b886fae9a58ef84f5942d8a08b19621cc2fe01b787ac10bff8b63393e6addaa178da4ea7 src/common/ gateway.py
compare_digest 618cbaccb4f50bc6e551cd13570693d4d53cfdfdc00b7ff42ff9fd14f0dadf7a15c9bc32c8e53801aca42348826568a2aa9117bdbf79bbcc4217a07c94c9efd3 src/common/ input.py
compare_digest 14f9a5f06443b50251272939b92eaf287fecfb4e3c9882254f13757259d9d791ac1e66c46fd0475d1e3161ff27f16ad7b4de04ac564500cae60d27a631d42e36 src/common/ misc.py
compare_digest b4efca327d057f9b3304d9f0415d23e95ba88230576a1a8d8be85c3e25364e7420389af36df4b4dacf62b5df11cb62f1ca7d0b6c03aee6b591282d51bbc119e3 src/common/ output.py
compare_digest 580c6453d8649d82973817de1fcfd83c73b0f6bb3e2597c5ffb6c0d3929d10f693c0a3d98dad4f528f1a1b62b220bcb99fa075ccd28e8b53d6cc966697523e25 src/common/ crypto.py
compare_digest 053c431755b4d6a5a869d2f06213bd23a8fb8743644b127d3622ff0223687880e8b1d510fbc976f2112318e48caa0044a1959c9af21272001e3f107fd9f785ba src/common/ db_contacts.py
compare_digest 8e4e8ab2dff2400f3fe72d980c95c1780d26e11f0482c555bb47cfa7513d091b06fd901666f415e0b3db291bd7f161d7182ac530a6d118a61f0b72bb665eead1 src/common/ db_groups.py
compare_digest 712c5422193994a65eff74f0a328107232069784c6c687f700a6823435fe65afb3e31cbd1102dbe07bc1d7e5e1572a4db6e467d477ebd3e4aab6ff3685723ccf src/common/ db_keys.py
compare_digest a2714c8aab538a5fc273f4eb58ea0d039effa4ce64559ab34dbe9eb92c762aff98a242443e2dffe5e55005b8778a5581f5c2ad3c07a0b6eee58e599e44a951b3 src/common/ db_logs.py
compare_digest c6bbb2b75f14447ba20bf1ac214b044d5a79b11a5346a1691a823c5e5ac4db05dd24a8de52856021d8d7f7f8582c1871839945323245521f320714cc72994bdf src/common/ db_masterkey.py
compare_digest 253521b1ed39a73a0fd6108cbcf88bbd1ffadde28be1467c1e7871094392d0a55947032ef5fc19d85727117bacf8516ac1f08f70240be20b9fc2d009a68989ee src/common/ db_onion.py
compare_digest 2e8ff65270e0165e510f5d330fa2cbfdc6ecf8ed953220bbe18d19a8afa6fa2bf852e56ba875c1c361fdb72016d0610e8c9fa1df302fa47f916eaaecde11e423 src/common/ db_settings.py
compare_digest 7a673e6feb7a5b2e3417d2c0eee82a59b3730a5d241938a84fd866dfc838c3cd63d7ef96772d43f62df740a2ba1001456746dd6c86e950484eac3ebabed498ce src/common/ encoding.py
compare_digest 00ad45d8fba1a605817a9f5d64cdfd6aad9c618db66befe682728a2291384c67bf5e80a5257211717a86e4a51c7e8c74f8f7ccccc3b4ac3c6f0f4c4e1b3cc98f src/common/ exceptions.py
compare_digest 5de68b10dc6b6ff98d7f73a2dc89d6b64c529fb4949b4a51d1b2fb4491006266bf7200b717102987347add666f12d7cfa9afc43a540304290f9a41fe48545078 src/common/ gateway.py
compare_digest 604893a2814219b2ed4e69b45d9ac2f8c2b5fc066bd085e86b76ef9df9984e6113f79fcaf3b9eb1197a9c9fc92cf524269e595d03b5009c46e8889d813475408 src/common/ input.py
compare_digest 1cac1bed0779f480de26054867cd732deaba5e7a46728ed8c203948ed92b96e0dbf2d5d1a696cbca445d5db2b79cde318aac28189d8806fe1ea530392a47f406 src/common/ misc.py
compare_digest e167f8458d1a1fc549f02a42ab9c1dc78ac2540b6a8660c77a06fe32de461eb52361a99582921b00eb50115c9fc70858dff1557c6e7ac69439768481a70b3fa7 src/common/ output.py
compare_digest c4d97b497b341f0e7865a4e27a2a2ffd3b3c5a7bfbf72f4676f6b65d6ba66a2adb8fed563f88fa25cef555f0042290ef0ae4cbeed1697a2e19a3b8cff0b9ef1b src/common/ path.py
compare_digest 9e9db25e73e0abb312b540d7601be7dfecae8d0aef6497feb2570f9ddf788fa0e261e276ed5a40e75fee0e0c6e86ccf3b05c110846fde804c302d4aa80a930e5 src/common/ reed_solomon.py
compare_digest 374a9846d2a1f4b462d9fd4b6fd468ecdaf3a1d1ac25916214e949604e83eff66a969a7fe6af79f23f4bf2477f7a390374714602714c42448073392e56065a41 src/common/ statics.py
compare_digest 4365ed3b6951525cb1ec8dc1177d7fd74d5dfa5eab1ca8934775391a8736eed4df039684f19ccc2d8022f20c8cf93a57a736b259e8c7235da5060c5f62057c98 src/common/ reed_solomon.py
compare_digest 1f26c39a8e5ec39a859e90dac2e38c08d86af02e0fc714aa0569b618dbf44de5befde601b0fec23e62e3e3b3f7281727abea37a02818c9994ac47f119a621386 src/common/ statics.py
compare_digest 339b402790cb3002841a1212d4dc24b07236b65baf68e3a8caf8d61bfd48af12887564be449ae49cfe5d7b88107a73e01a05053e3789398fbd560ef89f14afd4 src/common/ word_list.py
compare_digest cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e src/receiver/ __init__.py
compare_digest 12cf1127afc6761e61f6dadf6b6f382f4ff00291bcb00a6e9b21c98d1263596041b31892fbfc4be6348bb622aa19b50f38303d51a651d63c2024e8e466cbef07 src/receiver/ commands.py
compare_digest 760edaa44ff6175612b02f95b02b291ae369733a18cc5f87d525b46bcebc35c8a2d169a47962417eab434cf26ea6d5bfcd8894153fae668bb4a8cf2ceb8871f0 src/receiver/ commands_g.py
compare_digest ee192c3529c51031110a49cf919a7b9e516d0eb0cc826da85587323480065cf53d4115f78f8008c305d074628b9de65d9b262ef790c178078cf74d8b3466ae3f src/receiver/ files.py
compare_digest b3b0a61f3aba200e06bb2c6e05a067931be37ccfbc0768167a9d8651f7a8f6ea47871dcb5d63cf238f040d6c482fc45967a5a2ccbadb1308ec76b287b88fa3a6 src/receiver/ key_exchanges.py
compare_digest 65307a0ea2c9ae69859cc8ef62a5d7e45c27bdf5a4ec44db704df143ce3630fdc077fafc7fd4cfc0cd922f350f49f0aa0a880192c40c614b6d3117804ea683ae src/receiver/ messages.py
compare_digest b33a7696b31265027bb137aacd80845d4fefbd112144f3e01cfe4e083e5b6575fcdab387ae9d2435dd1c82bf0cf31b55f934cd12256a7ac78b4b13cc3d593987 src/receiver/ output_loop.py
compare_digest 7a81a142d21ebf8779fa0fec9aeef922d21bd6d57643061722d91047ecafeea7f0bfdd3a2919251a83a02cecbef909756fdb62881d5d2a844c3daba0e7069bf5 src/receiver/ packet.py
compare_digest 01b0ce92b4ab8f37eed55356a65f4d25ffff590955b5ca124dbb541bdec6968e8235242bf47ad5c2bfe57a30d54cf494d9f3836e3d53a73748f203ff68381c14 src/receiver/ receiver_loop.py
compare_digest 17ab4e845274ac6901902537cbea19841de56b5d6e92175599d22b74a3451ecafa3681420e2fcc7f53f6b2ebb7994e5903aabb63a5403fbf7fb892509bb5d11c src/receiver/ windows.py
compare_digest eaf664c520b7a2b4374e258b153940625312262fc54f9435018beff3152073d6dadcf78bedbe081a92cf173b31f2e86582c98a6065be1bba143b6b41b2d4b3b8 src/receiver/ commands.py
compare_digest 051a1eac8e1e177bdf1c94972ed511d56d9ccfb3dc97c6418355b4415b2d1dff42c4ef5420de05d90e1697375f4db119e04932c05d6a93a89e03e7ca4c7c7346 src/receiver/ commands_g.py
compare_digest 4a48adedcb839176e6a5f18b430a97c96558a7474d0840d22d31241a2918f1ffaed53211c5c949b545e231cda5280cf42fb5c20e22bdaef03f5fb2c298e22e07 src/receiver/ files.py
compare_digest 6eae2793bdd72b9581cbbebc012a70b11744c2585fda1d1e253ff4d67cdc1d316d1f3ad7e391f5197aa1447ce21b0ecbc3e34517a8932ecd9eec7ff5d7313b5b src/receiver/ key_exchanges.py
compare_digest d7d28808635a0425d231770b24c1a0f332ad40dffccfcf4f88801299b121e58363b728b0a70b0bc4d1640b850f7cb069ba88f3be966eb84f9e05f0112d3e76fd src/receiver/ messages.py
compare_digest d651d87311ec09b8aa0a3964718ea1ea22c5bc1cc078ca87a367b420fc19716438045b31618d34264f483eb009ed1aff33704d135d0d22374df31d5438f9e00c src/receiver/ output_loop.py
compare_digest a1edb6fe5b04117174ce45739e0737b6e8eedeed134bc95201d837bf6785bb98c86f8404eee18eb398755c8df113d7f8daa9dae43465846b887d514619759ed2 src/receiver/ packet.py
compare_digest 20c6754ddb6261c7a3b479e6ab7bf78eb0ef8783e2141373d7aba857f413091b78dcc9c32667dd8f8d5c41927102da7e35c4c4fcb0aa7376dc42b08c0c01d6e2 src/receiver/ receiver_loop.py
compare_digest 7d3f18351dc97bf4c1c5ab5894619d1f34ec07874758751340d53a12da2d7079944e6187d7436990f09a82d39c89d64a0bb747678c85ead8126eebbac9fcddfd src/receiver/ windows.py
compare_digest cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e src/relay/ __init__.py
compare_digest a324e4b065c2bb8c66c1e0e7fbc11de85f4408e4a3c3afd81bbd6829fae4f6cf6a3bba284864476356a57947670e7dffcd6f30faf03e38b38dbed84305def042 src/relay/ client.py
compare_digest 6f0f916f7c879c0383e8dc39593385de8f1ab2f7ba99f59b7fcbee947f8fcfae0935a98dcd8de1db3a600a0d8469a369ea38915fe2932c00b6756fb4944c938d src/relay/ commands.py
compare_digest 261c9a0e7cb552c900f5234136c6251896bb03277c9ceaf175791012433a282373d6af1eec6be916aa955e04d5ffde2fc4bf3d153077baf00731249132b90658 src/relay/ onion.py
compare_digest a18aa0ca4ffff7be99721c094ae44a95ed407e5e4cb25016ce14bf9fca7fef950d0b0460fd91689f2003aeb7f8385cb4f88a5f2e3f7f4ba7b88634412853d888 src/relay/ server.py
compare_digest e03b2896049d6c4f98cc9b52ae43a32078ffe93f6a18848fb96cf4051752872ad20463754fad61244e914b2082839d8297f0d3357e20c8dd22881e885bfbd32a src/relay/ tcb.py
compare_digest 5d34be330731b8b722c3580f12abd2515984ba0589ea95c0960ae099a13b9d66118a5af5cdf137bcf376bde88b0edf055888d2a5fc267081ea118fffc05a2b08 src/relay/ client.py
compare_digest c32b5b78e28567d5ef0c6f41f1a3c69f6d31b1cb3b9d58faf6516fa27fc62e12b2f359f7b60176b5fe20a2d94725f5fd76a879d4b795513d1588f8ecf9bae5b0 src/relay/ commands.py
compare_digest c72a57dda6054b9c020f694740751159df4602f11f7759ff76e48a8b7f07ec829b39d6c366613f3a69e36d3dca0823491f3232506f3a03ecc9ded3e2a4f0230a src/relay/ onion.py
compare_digest fe108f1f642bdfd01d813fd0a183e2f6039c1e64a5ee57f6159fdc67d7574a0ba0ee23608a2a8499071f0844b7d2db6b6a14740046d5d664e09856c35680a0dc src/relay/ server.py
compare_digest 9459e6cbe17fefac356e5ce183d923efff66f6d304111f2c0dbacdfb22a92df77bb11134faf8c15400bc59174ecbec1ea0b436065a9d49d3af70b46b24a77764 src/relay/ tcb.py
compare_digest cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e src/transmitter/ __init__.py
compare_digest bb471267d37c73b37f3695bd3f28929612fe2074f7303b77f40cba0be84e975e3498402cbba2b402e45e35e81512ed2f67629cf0584536f36903b326d35cf557 src/transmitter/ commands.py
compare_digest b861e16a6fbe2a93bf9f2dcf3f7f42d3f3ebf240aeaa2d752169a6807e2371eaaaf6230e0a71e6fe1e41e7ce5eb0515e8ec95bed5a7a7a263f42d715d23c399c src/transmitter/ commands_g.py
compare_digest d5ad53e3ba7ae32e0c6255f5fca3a60c267b028d7dfb2f2e28768c2f8824b0ad25afc7edc3a5d6d69bb76986ff8d81549d50d71d8b46ca6a0a9dc37324ae027a src/transmitter/ contact.py
compare_digest dffc059fc25cbfb17beb9f83fc2d52ce043e9b923580ccf655933cf66fefcf6e18bcb923d7cb42a7d547126f938ff867a7638ffd13c14953b4a2d700f8f0d5c4 src/transmitter/ files.py
compare_digest 061d7f3c4f737afafacc65bea744ac9bad16c6b064ab6281a3ab2079b18d984afa00b93e3861209425f78807e335925d824e018e80523b5380ac5244b8f5b8c2 src/transmitter/ input_loop.py
compare_digest 6465a819b1a449145fdb676819446761d8faa96921623433b12b9386b781d7964368c0f2cd5fe48aad5beda3cdc0f6496e95ae89650addd91619c5984979584c src/transmitter/ key_exchanges.py
compare_digest 1249ba23d30031ec8c1b42e999e52bc0903a0ad00c317d27d51c8d650b3343c9475319db6e0f2f16dfae39a942e01eeaae8eb862d541957b1f51690b1eb0f202 src/transmitter/ packet.py
compare_digest 93f874a8d97312ab4be10e803ba5b0432a40cf2c05541775c7117aa68c18e78e52e814d38639b33e87dc33df1773e04dc2c789e61e08c12d9c15512dd9e5d4d3 src/transmitter/ sender_loop.py
compare_digest bcad5d4b9932f1b35b2c74dc083233af584012cacdd1d2cb04b28115b4e224118ce220252754ae532df0adcc6c343b1913f711cf3bd94da9c4cd3eaf23e4b196 src/transmitter/ traffic_masking.py
compare_digest ccbda8415c23b23cc10cda57fb6b32df71e6510f3cb94c7f932b40adcf5f0abdd9842c48a992d56c95755e3024aebd7ecb05f69eb18f3c41656d94cfeabb38fa src/transmitter/ user_input.py
compare_digest 4c5b9c877474257d078723d32533ba614d72bad7b108588f49fae6c26dcb2c49994b256b5457caee0e5ab4628f728b25a54551ce778b585cbb8b1f8c947dc7e6 src/transmitter/ windows.py
compare_digest 2d002fe7aab987512534bdb18433d4a626b404aafc0360253056995559b0d2097938604325bc882a36ba456af0fbcf9620909945ebddf668bc44748a31a225c0 src/transmitter/ commands.py
compare_digest 74291c8b952588caf7c3c6ac3e99679eaf97ba9113bfd560da4c461e16cd36c28d78a4e8750090d18606a9c1610a1a781d37fc6fe52baf47a955e0b5ec801b97 src/transmitter/ commands_g.py
compare_digest 3a2940afcf8752f33c8f5a06293046a83d245630dde1a6877eb3c724cb03ee7b84147b4a57a62135a32862b40db1dc6c6823bedc52404146aeb6e9ef1f79692f src/transmitter/ contact.py
compare_digest 2e78e578e62771adf7ae9f2a576d72b69a64e6b28649361244bd7a75959f2022845d73d68c3d6a4586841bb10cce906edf1c5d863fbf99b6d081dfc030f98a3d src/transmitter/ files.py
compare_digest 7cb9fc9d095f40ce2de6b49c9bd58b9dcab6b835fe7749dce8642c3c87b0eee10c4e53ff986c09ae26fb7b8aad7fe87c5fd56a734f2e013f69195213b9d5e9ec src/transmitter/ input_loop.py
compare_digest e723db5bc403cec60b7df3e34d80caa7868bed4e8f0d08a6504d060fdd6c188f4afa41ecd8feb3a6520a384ccff7f8c64efeaeddb084f825d40e1854fb528f9f src/transmitter/ key_exchanges.py
compare_digest 41798dfe91868b37c130a373accac93c4200dc77bd8b6c40a38835ecf4187b955ccfaa53f842ccddf78ce5607b3e361a30a4bb53bd7cb5ab6d2fb4785454dead src/transmitter/ packet.py
compare_digest 3f1e7a5cb58ba8fcf0ccc66195d589ac0e34153296e2c395ca099304a99bb61c25248078dc453b3cc47e08a0b207a688b0a426d095df4a7bd235d1a95bb3c8d6 src/transmitter/ sender_loop.py
compare_digest c5a6c85e57d4456353f89fc4b2d30fc60775511720a32287720b3b301e0d6e7539677b47c4ff8c6b6f223b93da7dfbb38d1830f43e6f25c598efd54799262956 src/transmitter/ traffic_masking.py
compare_digest 678ae2b63667d93b1d4467d029ab04778614ddf6c09dff4bb61d262373353cd7fe6b8b535292fdf28e1be36c8b57534dee9eb745ee94c72b051798ac4e1cbccd src/transmitter/ user_input.py
compare_digest 00e247854f067194f80c86c9a3b9fbe1975e600844a1f33af79e36618680e0c9ddebaa25ef6df1a48e324e241f2b113f719fc29a2b43626eeeba4b92bdbb8528 src/transmitter/ windows.py
}
# PIP dependency file names
ARGON2=argon2_cffi-19.1.0-cp34-abi3-manylinux1_x86_64.whl
ASN1CRYPTO=asn1crypto-0.24.0-py2.py3-none-any.whl
CERTIFI=certifi-2019.6.16-py2.py3-none-any.whl
CFFI=cffi-1.12.3-cp37-cp37m-manylinux1_x86_64.whl
CHARDET=chardet-3.0.4-py2.py3-none-any.whl
CLICK=Click-7.0-py2.py3-none-any.whl
CRYPTOGRAPHY=cryptography-2.7-cp34-abi3-manylinux1_x86_64.whl
FLASK=Flask-1.1.1-py2.py3-none-any.whl
IDNA=idna-2.8-py2.py3-none-any.whl
ITSDANGEROUS=itsdangerous-1.1.0-py2.py3-none-any.whl
JINJA2=Jinja2-2.10.1-py2.py3-none-any.whl
MARKUPSAFE=MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl
PYCPARSER=pycparser-2.19.tar.gz
PYNACL=PyNaCl-1.3.0-cp34-abi3-manylinux1_x86_64.whl
PYSERIAL=pyserial-3.4-py2.py3-none-any.whl
PYSOCKS=PySocks-1.7.0-py3-none-any.whl
REQUESTS=requests-2.22.0-py2.py3-none-any.whl
SETUPTOOLS=setuptools-41.2.0-py2.py3-none-any.whl
SIX=six-1.12.0-py2.py3-none-any.whl
STEM=stem-1.7.1.tar.gz
URLLIB3=urllib3-1.25.3-py2.py3-none-any.whl
VIRTUALENV=virtualenv-16.7.3-py2.py3-none-any.whl
WERKZEUG=Werkzeug-0.15.5-py2.py3-none-any.whl
function process_tcb_dependencies {
# Manage TCB dependencies in batch. The command that uses the files
# is passed to the function as a parameter.
@ -145,7 +147,6 @@ function process_tcb_dependencies {
sudo $1 /opt/tfc/${SETUPTOOLS}
sudo $1 /opt/tfc/${PYNACL}
sudo $1 /opt/tfc/${PYSERIAL}
sudo $1 /opt/tfc/${ASN1CRYPTO}
sudo $1 /opt/tfc/${CRYPTOGRAPHY}
}
@ -154,37 +155,113 @@ function process_tails_dependencies {
# Manage Tails dependencies in batch. The command that uses the
# files is passed to the function as a parameter.
# Pyserial
t_sudo $1 /opt/tfc/${PYSERIAL}
# Stem
t_sudo $1 /opt/tfc/${STEM}
# PySocks
t_sudo $1 /opt/tfc/${PYSOCKS}
t_sudo -E $1 /opt/tfc/${PYSERIAL}
# t_sudo -E $1 /opt/tfc/${STEM}
t_sudo -E $1 /opt/tfc/${PYSOCKS}
# Requests
t_sudo $1 /opt/tfc/${URLLIB3}
t_sudo $1 /opt/tfc/${IDNA}
t_sudo $1 /opt/tfc/${CHARDET}
t_sudo $1 /opt/tfc/${CERTIFI}
t_sudo $1 /opt/tfc/${REQUESTS}
t_sudo -E $1 /opt/tfc/${URLLIB3}
t_sudo -E $1 /opt/tfc/${IDNA}
t_sudo -E $1 /opt/tfc/${CHARDET}
t_sudo -E $1 /opt/tfc/${CERTIFI}
t_sudo -E $1 /opt/tfc/${REQUESTS}
# Flask
t_sudo $1 /opt/tfc/${WERKZEUG}
t_sudo $1 /opt/tfc/${MARKUPSAFE}
t_sudo $1 /opt/tfc/${JINJA2}
t_sudo $1 /opt/tfc/${ITSDANGEROUS}
t_sudo $1 /opt/tfc/${CLICK}
t_sudo $1 /opt/tfc/${FLASK}
t_sudo -E $1 /opt/tfc/${WERKZEUG}
t_sudo -E $1 /opt/tfc/${MARKUPSAFE}
t_sudo -E $1 /opt/tfc/${JINJA2}
t_sudo -E $1 /opt/tfc/${ITSDANGEROUS}
t_sudo -E $1 /opt/tfc/${CLICK}
t_sudo -E $1 /opt/tfc/${FLASK}
# Cryptography
t_sudo $1 /opt/tfc/${SETUPTOOLS}
t_sudo $1 /opt/tfc/${SIX}
t_sudo $1 /opt/tfc/${ASN1CRYPTO}
t_sudo $1 /opt/tfc/${PYCPARSER}
t_sudo $1 /opt/tfc/${CFFI}
t_sudo $1 /opt/tfc/${CRYPTOGRAPHY}
t_sudo -E $1 /opt/tfc/${SIX}
t_sudo -E $1 /opt/tfc/${PYCPARSER}
t_sudo -E $1 /opt/tfc/${CFFI}
t_sudo -E $1 /opt/tfc/${CRYPTOGRAPHY}
# PyNaCl
t_sudo -E $1 /opt/tfc/${PYNACL}
}
function move_tails_dependencies {
# Move Tails dependencies in batch.
t_sudo mv $HOME/${VIRTUALENV} /opt/tfc/
t_sudo mv $HOME/${PYSERIAL} /opt/tfc/
# t_sudo mv $HOME/${STEM} /opt/tfc/
t_sudo mv $HOME/${PYSOCKS} /opt/tfc/
# Requests
t_sudo mv $HOME/${URLLIB3} /opt/tfc/
t_sudo mv $HOME/${IDNA} /opt/tfc/
t_sudo mv $HOME/${CHARDET} /opt/tfc/
t_sudo mv $HOME/${CERTIFI} /opt/tfc/
t_sudo mv $HOME/${REQUESTS} /opt/tfc/
# Flask
t_sudo mv $HOME/${WERKZEUG} /opt/tfc/
t_sudo mv $HOME/${MARKUPSAFE} /opt/tfc/
t_sudo mv $HOME/${JINJA2} /opt/tfc/
t_sudo mv $HOME/${ITSDANGEROUS} /opt/tfc/
t_sudo mv $HOME/${CLICK} /opt/tfc/
t_sudo mv $HOME/${FLASK} /opt/tfc/
# Cryptography
t_sudo mv $HOME/${SIX} /opt/tfc/
t_sudo mv $HOME/${PYCPARSER} /opt/tfc/
t_sudo mv $HOME/${CFFI} /opt/tfc/
t_sudo mv $HOME/${CRYPTOGRAPHY} /opt/tfc/
# PyNaCl
t_sudo mv $HOME/${PYNACL} /opt/tfc/
}
function verify_tails_dependencies {
# Tails doesn't allow downloading over PIP to /opt/tfc, so we
# first download to $HOME, move the files to /opt/tfc, and then
# perform additional hash verification
compare_digest e80eb04615d1dcd2546bd5ceef5408bbb577fa0dd725bc69f20dd7840518af575f0b41e629e8164fdaea398628813720a6f70a42e7748336601391605b79f542 '' ${VIRTUALENV}
compare_digest 8333ac2843fd136d5d0d63b527b37866f7d18afc3bb33c4938b63af077492aeb118eb32a89ac78547f14d59a2adb1e5d00728728275de62317da48dadf6cdff9 '' ${PYSERIAL}
# compare_digest a275f59bba650cb5bb151cf53fb1dd820334f9abbeae1a25e64502adc854c7f54c51bc3d6c1656b595d142fc0695ffad53aab3c57bc285421c1f4f10c9c3db4c '' ${STEM}
compare_digest 313b954102231d038d52ab58f41e3642579be29f827135b8dd92c06acb362effcb0a7fd5f35de9273372b92d9fe29f38381ae44f8b41aa90d2564d6dd07ecd12 '' ${PYSOCKS}
# Requests
compare_digest 719cfa3841d0fe7c7f0a1901b8029df6685825da7f510ba61f095df64f115fae8bfa4118fa7536231ed8187cdf3385cb2d52e53c1b35b8f4aa42f7117cc4d447 '' ${URLLIB3}
compare_digest fb07dbec1de86efbad82a4f73d98123c59b083c1f1277445204bef75de99ca200377ad2f1db8924ae79b31b3dd984891c87d0a6344ec4d07a0ddbbbc655821a3 '' ${IDNA}
compare_digest bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4 '' ${CHARDET}
compare_digest 06e8e1546d375e528a1486e1dee4fda3e585a03ef23ede85d1dad006e0eda837ebade1edde62fdc987a7f310bda69159e94ec36b79a066e0e13bbe8bf7019cfc '' ${CERTIFI}
compare_digest 9186ce4e39bb64f5931a205ffc9afac61657bc42078bc4754ed12a2b66a12b7a620583440849fc2e161d1061ac0750ddef4670f54916931ace1e9abd2a9fb09c '' ${REQUESTS}
# Flask
compare_digest 3905022d0c398856b30d2ed6bae046c1532e87f56a0a40060030c18124c6c9c98976d9429e2ab03676c4ce75be4ea915ffc2719e04e4b4912a96e498dcd9eb89 '' ${WERKZEUG}
compare_digest 69e9b9c9ac4fdf3cfa1a3de23d14964b843989128f8cc6ea58617fc5d6ef937bcc3eae9cb32b5164b5f54b06f96bdff9bc249529f20671cc26adc9e6ce8f6bec '' ${MARKUPSAFE}
compare_digest 658d069944c81f9d8b2e90577a9d2c844b4c6a26764efefd7a86f26c05276baf6c7255f381e20e5178782be1786b7400cab12dec15653e7262b36194228bf649 '' ${JINJA2}
compare_digest 891c294867f705eb9c66274bd04ac5d93140d6e9beea6cbf9a44e7f9c13c0e2efa3554bdf56620712759a5cd579e112a782d25f3f91ba9419d60b2b4d2bc5b7c '' ${ITSDANGEROUS}
compare_digest 6b30987349df7c45c5f41cff9076ed45b178b444fca1ab1965f4ae33d1631522ce0a2868392c736666e83672b8b20e9503ae9ce5016dce3fa8f77bc8a3674130 '' ${CLICK}
compare_digest bd49cb364307569480196289fa61fbb5493e46199620333f67617367278e1f56b20fc0d40fd540bef15642a8065e488c24e97f50535e8ec143875095157d8069 '' ${FLASK}
# Cryptography
compare_digest 326574c7542110d2cd8071136a36a6cffc7637ba948b55e0abb7f30f3821843073223301ecbec1d48b8361b0d7ccb338725eeb0424696efedc3f6bd2a23331d3 '' ${SIX}
compare_digest 7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5 '' ${PYCPARSER}
compare_digest fdefd3f63f56adff50723d6a88dc6db816d3d8a31b563599d2a3633ba796f6f70d5a9430510852b3d62b97357f8764f17eeab74b13df16c7cc34e1671a82373b '' ${CFFI}
compare_digest 184003c89fee74892de25c3e5ec366faea7a5f1fcca3c82b0d5e5f9f797286671a820ca54da5266d6f879ab342c97e25bce9db366c5fb1178690cd5978d4d622 '' ${CRYPTOGRAPHY} # manylinux1
# compare_digest d8ddabe127ae8d7330d219e284de68b37fa450a27b4cf05334e9115388295b00148d9861c23b1a2e5ea9df0c33a2d27f3e4b25ce9abd3c334f1979920b19c902 '' ${CRYPTOGRAPHY} # manylinux2010
# PyNaCl
compare_digest c4017c38b026a5c531b15839b8d61d1fae9907ba1960c2f97f4cd67fe0827729346d5186a6d6927ba84f64b4cbfdece12b287aa7750a039f4160831be871cea3 '' ${PYNACL}
}
function install_tails_setuptools {
# Download setuptools package for Tails and then authenticate and install it.
torsocks python3.7 -m pip download --no-cache-dir -r /opt/tfc/requirements-setuptools.txt --require-hashes -d $HOME/
t_sudo mv $HOME/${SETUPTOOLS} /opt/tfc/
compare_digest a27b38d596931dfef81d705d05689b7748ce0e02d21af4a37204fc74b0913fa7241b8135535eb7749f09af361cad90c475af98493fef11c4ad974780ee01243d '' ${SETUPTOOLS}
t_sudo python3.7 -m pip install /opt/tfc/${SETUPTOOLS}
t_sudo -E rm /opt/tfc/${SETUPTOOLS}
}
@ -203,6 +280,8 @@ function remove_common_files {
$1 rm /opt/tfc/requirements.txt
$1 rm /opt/tfc/requirements-dev.txt
$1 rm /opt/tfc/requirements-relay.txt
$1 rm /opt/tfc/requirements-relay-tails.txt
$1 rm /opt/tfc/requirements-setuptools.txt
$1 rm /opt/tfc/requirements-venv.txt
$1 rm -f /opt/install.sh
$1 rm -f /opt/install.sh.asc
@ -222,7 +301,7 @@ function steps_before_network_kill {
check_rm_existing_installation
sudo torsocks apt update
sudo torsocks apt install git libssl-dev python3-pip python3-tk net-tools -y
sudo torsocks apt install git gnome-terminal libssl-dev python3-pip python3-tk net-tools -y
sudo torsocks git clone --depth 1 https://github.com/maqp/tfc.git /opt/tfc
verify_tcb_requirements_files
@ -261,6 +340,7 @@ function install_tcb {
sudo rm -r /opt/tfc/src/relay/
sudo rm /opt/tfc/dd.py
sudo rm /opt/tfc/relay.py
sudo rm /opt/tfc/tfc.yml
sudo rm /opt/tfc/${VIRTUALENV}
add_serial_permissions
@ -294,6 +374,7 @@ function install_local_test {
# Remove unnecessary files
remove_common_files "sudo"
process_tcb_dependencies "rm"
sudo rm /opt/tfc/tfc.yml
sudo rm /opt/tfc/${VIRTUALENV}
install_complete "Installation of TFC for local testing is now complete."
@ -319,11 +400,10 @@ function install_developer {
torsocks git clone https://github.com/maqp/tfc.git $HOME/tfc
torsocks python3.7 -m pip install -r $HOME/tfc/requirements-venv.txt --require-hashes
python3.7 -m virtualenv $HOME/tfc/venv_tfc --system-site-packages
. $HOME/tfc/venv_tfc/bin/activate
torsocks python3.7 -m pip install -r $HOME/tfc/requirements.txt --require-hashes
torsocks python3.7 -m pip install -r $HOME/tfc/requirements-relay.txt --require-hashes
torsocks python3.7 -m pip install -r $HOME/tfc/requirements-dev.txt
deactivate
@ -368,6 +448,7 @@ function install_relay_ubuntu {
sudo rm -r /opt/tfc/src/transmitter/
sudo rm /opt/tfc/dd.py
sudo rm /opt/tfc/tfc.py
sudo rm /opt/tfc/tfc.yml
sudo rm /opt/tfc/${VIRTUALENV}
add_serial_permissions
@ -380,28 +461,45 @@ function install_relay_tails {
# Install TFC Relay configuration on Networked Computer running
# Tails live distro (https://tails.boum.org/).
check_tails_tor_version
read_sudo_pwd
# Apt dependencies
t_sudo apt update
t_sudo apt install git libssl-dev python3-pip -y || true # Ignore error in case packets can not be persistently installed
git clone --depth 1 https://github.com/maqp/tfc.git $HOME/tfc
torsocks git clone --depth 1 https://github.com/maqp/tfc.git $HOME/tfc
t_sudo mv $HOME/tfc/ /opt/tfc/
t_sudo chown -R root /opt/tfc/
verify_tcb_requirements_files
verify_files
create_user_data_dir
t_sudo python3.7 -m pip download --no-cache-dir -r /opt/tfc/requirements-relay.txt --require-hashes -d /opt/tfc/
install_tails_setuptools
torsocks python3.7 -m pip download --no-cache-dir -r /opt/tfc/requirements-venv.txt --require-hashes -d $HOME/
torsocks python3.7 -m pip download --no-cache-dir -r /opt/tfc/requirements-relay-tails.txt --require-hashes -d $HOME/
move_tails_dependencies
verify_tails_dependencies
t_sudo python3.7 -m pip install /opt/tfc/${VIRTUALENV}
t_sudo python3.7 -m virtualenv /opt/tfc/venv_relay --system-site-packages
. /opt/tfc/venv_relay/bin/activate
process_tails_dependencies "python3.7 -m pip install"
deactivate
# Complete setup
t_sudo mv /opt/tfc/tfc.png /usr/share/pixmaps/
t_sudo mv /opt/tfc/launchers/TFC-RP-Tails.desktop /usr/share/applications/
t_sudo mv /opt/tfc/tfc.yml /etc/onion-grater.d/
remove_common_files "t_sudo"
process_tails_dependencies "rm"
t_sudo rm /opt/tfc/${VIRTUALENV}
t_sudo rm -r /opt/tfc/src/receiver/
t_sudo rm -r /opt/tfc/src/transmitter/
t_sudo rm /opt/tfc/dd.py
@ -419,7 +517,7 @@ function t_sudo {
function install_relay {
# Determine the Networked Computer OS for Relay Program installation.
if [[ "$(lsb_release -a 2>/dev/null | grep Tails)" ]]; then
if [[ "$(cat /etc/os-release 2>/dev/null | grep Tails)" ]]; then
install_relay_tails
else
install_relay_ubuntu
@ -428,16 +526,10 @@ function install_relay {
function install_virtualenv {
# Determine if OS is debian and install virtualenv as sudo so that
# the user (who should be on sudoers list) can see virtualenv on
# when the installer sets up virtual environment to /opt/tfc/.
distro=$(lsb_release -d | awk -F"\t" '{print $2}')
if [[ "$distro" =~ ^Debian* ]]; then
sudo torsocks python3.7 -m pip install -r /opt/tfc/requirements-venv.txt --require-hashes
else
torsocks python3.7 -m pip install -r /opt/tfc/requirements-venv.txt --require-hashes
fi
# Some distros want virtualenv installed as sudo and other do
# not. Install both to improve the chances of compatibility.
sudo torsocks python3.7 -m pip install -r /opt/tfc/requirements-venv.txt --require-hashes
torsocks python3.7 -m pip install -r /opt/tfc/requirements-venv.txt --require-hashes
}
@ -624,6 +716,12 @@ function root_check {
function sudoer_check {
# Check that the user who launched the installer is on the sudoers list.
# Tails allows sudo without the user `amnesia` being on sudoers list.
if ! [[ "$(lsb_release -a 2>/dev/null | grep Tails)" ]]; then
return
fi
sudoers=$(getent group sudo |cut -d: -f4 | tr "," "\n")
user_is_sudoer=false

View File

@ -1,16 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEE6o84umdLJC6ZRIRcmBNw6XJaD7oFAlxJLVgACgkQmBNw6XJa
D7rz2g//S9fHYr2YHtkW35pywZz2OqrzEjeQUgLkSrDrV6DSgKiuAnVJdqGUzu47
xvlGXV7w7dGAppvSU7J0kd/b2LXSdB8cPyBC+yoxPt+Uym6+KA79JzggHQ9j2uFR
2st7+Gjdl/UjpP2KL4dyDyBGAuE8rDUhTMsVc7BbYIx2d3PTD6W6N6wbq97Wdjxu
aqq589jd12NIxsJbPMcNhoc9Yw38WlI4vEZaQClVjkyZA6iA4fV/cevLYeR377Cu
61NLByJ/rAfTmcHgbruNuQPUtoinV07eoPmhmpvT8UFpuH05/9+UVikl/1BbhpcX
P279oawjcjF4ZUCQDNtOHRAn+PcVBQ8GeZeN4USqlWm4m14tEdkk5hy5DaQjfmU7
jco5SrAcWiK1wuIUdc2Z4y2heuZCGP1a1POYfBDjiS6IvXo8tMrkl/sgFlpA72lx
rbJQBRffsWkropvzrdkWo0ShkMYcmUhbUiUstUmML2L7OrI4o/wEc6ZE29MELqnC
nRrcIq7PRchJ/AHg99MWHZA74H106cWBGI82SiJsiShuQDK2S8SvvhVQ0nqPVu8B
o9GnUbuFGooT9HKjZGUVGlSE6VLArb9zzUPTe2BBgV/oRc7+DHH2P4O1VV29jbCt
sAn4wi3Eoah7igulEvvnF6kjdZ3vlkRJv/lDHc4Tz7w1CZY28yI=
=yumw
iQIzBAABCAAdFiEE6o84umdLJC6ZRIRcmBNw6XJaD7oFAlxJMGkACgkQmBNw6XJa
D7pbig//f0vdjRENVNXjVzrtr93ZWiKalltzz6tQJwRMS4Nkc6MlfVI+yEx4ahMP
cFpmRDrUov/g1F5SJYQhiaQVIsM61CLPVHnAilJmGI2g9hx8zmd6pM+ax+JeMmi5
RSwW1ZrYfNUExLa50XtoCRf/UGFsRoG1u1Ri0xmEWJMMiYfMggFpzynhiitRBeEK
vnHdIRf0b1ZsMmoW6PGPXM6NT4swT75xbI+/MfRg2YotbczEgJFz9WLdPTZBOo/9
3SvqfMnc+XfbY+GYpgJpD397cY6BZ/qzTge7ffn8YfaM9X0p+M27teyQjHmmpm3t
PVDSyR8f0RIV+8LtizQ5dbJ1tpG1S1UP6eMvupBuiL9LPXtpgLMByIDnfZg1czMS
llaOVbCSbFEb2yioAh+r4G/R1geKZOjmPAt9Sddlf/QakH3Jqj7+SUE/kvSHBzoV
Fcu3sgmrbCp2fWuhyU868471DkGY4MlNYDCLdYxXT7UpRoEryGo+mkp6G4ZDeorT
DK4mx3JKSw+OWVyKOXqhECUWBT/7w9CRC8GfVraan/a4dHaTkoPPwtBGqfP3T2Z/
NiVvtPCryU2qk762sYXh1cLLRWo6BKIU6VHWQ0CDJqPWhktMXs/zWxH+MDGFoiLw
bKEbOMYZkYEy+2BpjYh19W0nQAk52t/smOM+ehhNiH8jhUiJwUM=
=thVJ
-----END PGP SIGNATURE-----

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
[Desktop Entry]
Version=1.19.08
Version=1.19.10
Name=TFC-Relay
Exec=gnome-terminal -x bash -c "cd /opt/tfc && source venv_relay/bin/activate && python3.7 'relay.py' && deactivate || bash"
Icon=tfc.png

View File

@ -1,5 +1,5 @@
[Desktop Entry]
Version=1.19.08
Version=1.19.10
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"
Icon=tfc.png

View File

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

View File

@ -31,10 +31,10 @@ from cryptography.hazmat.primitives.serialization import Encoding, PublicForma
from src.common.gateway import Gateway, gateway_loop
from src.common.misc import ensure_dir, monitor_processes, process_arguments
from src.common.output import print_title
from src.common.statics import C_REQ_MGMT_QUEUE, C_REQ_STATE_QUEUE, CONTACT_MGMT_QUEUE, CONTACT_REQ_QUEUE, DIR_TFC
from src.common.statics import DST_COMMAND_QUEUE, DST_MESSAGE_QUEUE, EXIT_QUEUE, F_TO_FLASK_QUEUE, GATEWAY_QUEUE
from src.common.statics import GROUP_MGMT_QUEUE, GROUP_MSG_QUEUE, M_TO_FLASK_QUEUE, NC, ONION_CLOSE_QUEUE
from src.common.statics import ONION_KEY_QUEUE, SRC_TO_RELAY_QUEUE, TOR_DATA_QUEUE, URL_TOKEN_QUEUE
from src.common.statics import (CONTACT_MGMT_QUEUE, CONTACT_REQ_QUEUE, C_REQ_MGMT_QUEUE, C_REQ_STATE_QUEUE, DIR_TFC,
DST_COMMAND_QUEUE, DST_MESSAGE_QUEUE, EXIT_QUEUE, F_TO_FLASK_QUEUE, GATEWAY_QUEUE,
GROUP_MGMT_QUEUE, GROUP_MSG_QUEUE, M_TO_FLASK_QUEUE, NC, ONION_CLOSE_QUEUE,
ONION_KEY_QUEUE, SRC_TO_RELAY_QUEUE, TOR_DATA_QUEUE, URL_TOKEN_QUEUE)
from src.relay.client import c_req_manager, client_scheduler, g_msg_manager
from src.relay.commands import relay_command

View File

@ -1,7 +1,46 @@
# Static type checking tool
mypy
mypy>=0.740
# Unit test tools
pytest
pytest-cov
pytest-xdist
pytest>=5.2.1
pytest-cov>=2.8.1
pytest-xdist>=1.30.0
# TFC dependencies (note: not authenticated with hashes)
# pyserial
pyserial>=3.4
# argon2_cffi
argon2_cffi>=19.1.0
cffi>=1.13.1
pycparser>=2.19
six>=1.12.0
# pyca/pynacl
PyNaCl>=1.3.0
setuptools>=41.4.0
# pyca/cryptography
cryptography>=2.8
# Stem
stem>=1.7.1
# PySocks
pysocks>=1.7.1
# Requests
requests>=2.22.0
certifi>=2019.9.11
chardet>=3.0.4
idna>=2.8
urllib3>=1.25.6
# Flask
flask>=1.1.1
click>=7.0
itsdangerous>=1.1.0
jinja2>=2.10.3
markupsafe>=1.1.1
werkzeug>=0.16.0

View File

@ -0,0 +1,38 @@
# Sub-dependencies are listed below dependencies
# Pyserial (Connects the Source/Destination Computer to the Networked Computer)
pyserial==3.4 --hash=sha512:8333ac2843fd136d5d0d63b527b37866f7d18afc3bb33c4938b63af077492aeb118eb32a89ac78547f14d59a2adb1e5d00728728275de62317da48dadf6cdff9
# Stem (Connects to Tor and manages Onion Services)
# stem==1.7.1 --hash=sha512:a275f59bba650cb5bb151cf53fb1dd820334f9abbeae1a25e64502adc854c7f54c51bc3d6c1656b595d142fc0695ffad53aab3c57bc285421c1f4f10c9c3db4c
# PySocks (Routes requests library through SOCKS5 proxy making Onion Service connections possible)
pysocks==1.7.1 --hash=sha512:313b954102231d038d52ab58f41e3642579be29f827135b8dd92c06acb362effcb0a7fd5f35de9273372b92d9fe29f38381ae44f8b41aa90d2564d6dd07ecd12
# Requests (Connects to the contact's Tor Onion Service)
requests==2.22.0 --hash=sha512:9186ce4e39bb64f5931a205ffc9afac61657bc42078bc4754ed12a2b66a12b7a620583440849fc2e161d1061ac0750ddef4670f54916931ace1e9abd2a9fb09c
certifi==2019.9.11 --hash=sha512:06e8e1546d375e528a1486e1dee4fda3e585a03ef23ede85d1dad006e0eda837ebade1edde62fdc987a7f310bda69159e94ec36b79a066e0e13bbe8bf7019cfc
chardet==3.0.4 --hash=sha512:bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4
idna==2.8 --hash=sha512:fb07dbec1de86efbad82a4f73d98123c59b083c1f1277445204bef75de99ca200377ad2f1db8924ae79b31b3dd984891c87d0a6344ec4d07a0ddbbbc655821a3
urllib3==1.25.6 --hash=sha512:719cfa3841d0fe7c7f0a1901b8029df6685825da7f510ba61f095df64f115fae8bfa4118fa7536231ed8187cdf3385cb2d52e53c1b35b8f4aa42f7117cc4d447
# Flask (Onion Service web server that serves TFC public keys and ciphertexts to contacts)
flask==1.1.1 --hash=sha512:bd49cb364307569480196289fa61fbb5493e46199620333f67617367278e1f56b20fc0d40fd540bef15642a8065e488c24e97f50535e8ec143875095157d8069
click==7.0 --hash=sha512:6b30987349df7c45c5f41cff9076ed45b178b444fca1ab1965f4ae33d1631522ce0a2868392c736666e83672b8b20e9503ae9ce5016dce3fa8f77bc8a3674130
itsdangerous==1.1.0 --hash=sha512:891c294867f705eb9c66274bd04ac5d93140d6e9beea6cbf9a44e7f9c13c0e2efa3554bdf56620712759a5cd579e112a782d25f3f91ba9419d60b2b4d2bc5b7c
jinja2==2.10.3 --hash=sha512:658d069944c81f9d8b2e90577a9d2c844b4c6a26764efefd7a86f26c05276baf6c7255f381e20e5178782be1786b7400cab12dec15653e7262b36194228bf649
markupsafe==1.1.1 --hash=sha512:69e9b9c9ac4fdf3cfa1a3de23d14964b843989128f8cc6ea58617fc5d6ef937bcc3eae9cb32b5164b5f54b06f96bdff9bc249529f20671cc26adc9e6ce8f6bec
werkzeug==0.16.0 --hash=sha512:3905022d0c398856b30d2ed6bae046c1532e87f56a0a40060030c18124c6c9c98976d9429e2ab03676c4ce75be4ea915ffc2719e04e4b4912a96e498dcd9eb89
# Cryptography (Handles URL token derivation)
cryptography==2.8 --hash=sha512:184003c89fee74892de25c3e5ec366faea7a5f1fcca3c82b0d5e5f9f797286671a820ca54da5266d6f879ab342c97e25bce9db366c5fb1178690cd5978d4d622
cffi==1.13.1 --hash=sha512:fdefd3f63f56adff50723d6a88dc6db816d3d8a31b563599d2a3633ba796f6f70d5a9430510852b3d62b97357f8764f17eeab74b13df16c7cc34e1671a82373b
pycparser==2.19 --hash=sha512:7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5
six==1.12.0 --hash=sha512:326574c7542110d2cd8071136a36a6cffc7637ba948b55e0abb7f30f3821843073223301ecbec1d48b8361b0d7ccb338725eeb0424696efedc3f6bd2a23331d3
# PyNaCl (Derives TFC account from Onion Service private key)
PyNaCl==1.3.0 --hash=sha512:c4017c38b026a5c531b15839b8d61d1fae9907ba1960c2f97f4cd67fe0827729346d5186a6d6927ba84f64b4cbfdece12b287aa7750a039f4160831be871cea3
# Duplicate sub-dependencies
# cffi==1.13.1 --hash=sha512:fdefd3f63f56adff50723d6a88dc6db816d3d8a31b563599d2a3633ba796f6f70d5a9430510852b3d62b97357f8764f17eeab74b13df16c7cc34e1671a82373b
# pycparser==2.19 --hash=sha512:7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5
# six==1.12.0 --hash=sha512:326574c7542110d2cd8071136a36a6cffc7637ba948b55e0abb7f30f3821843073223301ecbec1d48b8361b0d7ccb338725eeb0424696efedc3f6bd2a23331d3

View File

@ -7,34 +7,33 @@ pyserial==3.4 --hash=sha512:8333ac2843fd136d5d0d63b527b37866f7d18afc3bb33c
stem==1.7.1 --hash=sha512:a275f59bba650cb5bb151cf53fb1dd820334f9abbeae1a25e64502adc854c7f54c51bc3d6c1656b595d142fc0695ffad53aab3c57bc285421c1f4f10c9c3db4c
# PySocks (Routes requests library through SOCKS5 proxy making Onion Service connections possible)
pysocks==1.7.0 --hash=sha512:5bbffb2714a04fb53417058703d8112c5e5dca768df627e64618e8ab8a36a8bdbc27f5d6852f39cff6b8fb4c9a5d13909f86eeb5fe9741ba42bdc985685e5d51
pysocks==1.7.1 --hash=sha512:313b954102231d038d52ab58f41e3642579be29f827135b8dd92c06acb362effcb0a7fd5f35de9273372b92d9fe29f38381ae44f8b41aa90d2564d6dd07ecd12
# Requests (Connects to the contact's Tor Onion Service)
requests==2.22.0 --hash=sha512:9186ce4e39bb64f5931a205ffc9afac61657bc42078bc4754ed12a2b66a12b7a620583440849fc2e161d1061ac0750ddef4670f54916931ace1e9abd2a9fb09c
certifi==2019.6.16 --hash=sha512:d81fe3a75ea611466d5ece7788f47c7946a4226bf4622c2accfd28c1e37b817e748609710c176c51ef2621cbc7ee200dd8d8106e738f1ef7cb96d7f2f82539cc
certifi==2019.9.11 --hash=sha512:06e8e1546d375e528a1486e1dee4fda3e585a03ef23ede85d1dad006e0eda837ebade1edde62fdc987a7f310bda69159e94ec36b79a066e0e13bbe8bf7019cfc
chardet==3.0.4 --hash=sha512:bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4
idna==2.8 --hash=sha512:fb07dbec1de86efbad82a4f73d98123c59b083c1f1277445204bef75de99ca200377ad2f1db8924ae79b31b3dd984891c87d0a6344ec4d07a0ddbbbc655821a3
urllib3==1.25.3 --hash=sha512:46d144af3633080b9ec8a642ab855b401b8224edb839c237639998b004f19b8cb191155c57e633954cf70b100d6d8b21105cd280acd1ea975aef1dec9a4a5860
urllib3==1.25.6 --hash=sha512:719cfa3841d0fe7c7f0a1901b8029df6685825da7f510ba61f095df64f115fae8bfa4118fa7536231ed8187cdf3385cb2d52e53c1b35b8f4aa42f7117cc4d447
# Flask (Onion Service web server that serves TFC public keys and ciphertexts to contacts)
flask==1.1.1 --hash=sha512:bd49cb364307569480196289fa61fbb5493e46199620333f67617367278e1f56b20fc0d40fd540bef15642a8065e488c24e97f50535e8ec143875095157d8069
click==7.0 --hash=sha512:6b30987349df7c45c5f41cff9076ed45b178b444fca1ab1965f4ae33d1631522ce0a2868392c736666e83672b8b20e9503ae9ce5016dce3fa8f77bc8a3674130
itsdangerous==1.1.0 --hash=sha512:891c294867f705eb9c66274bd04ac5d93140d6e9beea6cbf9a44e7f9c13c0e2efa3554bdf56620712759a5cd579e112a782d25f3f91ba9419d60b2b4d2bc5b7c
jinja2==2.10.1 --hash=sha512:04860c7ff7086f051368787289f75198eec3357c7da7565dc5045353122650a887e063b1a5297578ddefcc77bfdfe3d9a23c868cb3e7f18a0b5f1c475e29339e
jinja2==2.10.3 --hash=sha512:658d069944c81f9d8b2e90577a9d2c844b4c6a26764efefd7a86f26c05276baf6c7255f381e20e5178782be1786b7400cab12dec15653e7262b36194228bf649
markupsafe==1.1.1 --hash=sha512:69e9b9c9ac4fdf3cfa1a3de23d14964b843989128f8cc6ea58617fc5d6ef937bcc3eae9cb32b5164b5f54b06f96bdff9bc249529f20671cc26adc9e6ce8f6bec
werkzeug==0.15.5 --hash=sha512:19728875a846f895b7e20f1e8762455147253b295c29e4fb981f734a7ec6a491ae4a5427b0fcac54013c9fcca3d9a53d2639c00a0913c8d9ce69d8e8e24cab42
werkzeug==0.16.0 --hash=sha512:3905022d0c398856b30d2ed6bae046c1532e87f56a0a40060030c18124c6c9c98976d9429e2ab03676c4ce75be4ea915ffc2719e04e4b4912a96e498dcd9eb89
# Cryptography (Handles URL token derivation)
cryptography==2.7 --hash=sha512:1285c3f5181da41bace4f9fd5ce5fc4bfba71143b39a4f3d8bab642db65bec9556b1965b1c2990236fed9d6b156bf81e6c0642d1531eadf7b92379c25cc4aeac
asn1crypto==0.24.0 --hash=sha512:8d9bc344981079ac6c00e71e161c34b6f403e575bbfe1ad06e30a3bcb33e0db317bdcb7aed2d18d510cb1b3ee340a649f7f77a00d271fcf3cc388e6655b67533
cffi==1.12.3 --hash=sha512:69a2d725395a1a3585556cb44b62c49bd7f88f41ff194b60d4b9b591c4878a907c0770ef4052b588eaa9d420a53cbeb6b13237fff4054bf26ba5deaa84e25afa
cryptography==2.8 --hash=sha512:184003c89fee74892de25c3e5ec366faea7a5f1fcca3c82b0d5e5f9f797286671a820ca54da5266d6f879ab342c97e25bce9db366c5fb1178690cd5978d4d622
cffi==1.13.1 --hash=sha512:fdefd3f63f56adff50723d6a88dc6db816d3d8a31b563599d2a3633ba796f6f70d5a9430510852b3d62b97357f8764f17eeab74b13df16c7cc34e1671a82373b
pycparser==2.19 --hash=sha512:7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5
six==1.12.0 --hash=sha512:326574c7542110d2cd8071136a36a6cffc7637ba948b55e0abb7f30f3821843073223301ecbec1d48b8361b0d7ccb338725eeb0424696efedc3f6bd2a23331d3
# PyNaCl (Derives TFC account from Onion Service private key)
PyNaCl==1.3.0 --hash=sha512:c4017c38b026a5c531b15839b8d61d1fae9907ba1960c2f97f4cd67fe0827729346d5186a6d6927ba84f64b4cbfdece12b287aa7750a039f4160831be871cea3
setuptools==41.2.0 --hash=sha512:125341f0c22e11d2bd24c453b22e8fd7fd71605ee7a44eb61228686326eaca2e8f35b7ad4d0eacde4865f4d8cb8acb5cb5e3ff2856e756632b71af2f0dbdbee9
setuptools==41.4.0 --hash=sha512:a27b38d596931dfef81d705d05689b7748ce0e02d21af4a37204fc74b0913fa7241b8135535eb7749f09af361cad90c475af98493fef11c4ad974780ee01243d
# Duplicate sub-dependencies
# cffi==1.12.3 --hash=sha512:69a2d725395a1a3585556cb44b62c49bd7f88f41ff194b60d4b9b591c4878a907c0770ef4052b588eaa9d420a53cbeb6b13237fff4054bf26ba5deaa84e25afa
# cffi==1.13.1 --hash=sha512:fdefd3f63f56adff50723d6a88dc6db816d3d8a31b563599d2a3633ba796f6f70d5a9430510852b3d62b97357f8764f17eeab74b13df16c7cc34e1671a82373b
# pycparser==2.19 --hash=sha512:7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5
# six==1.12.0 --hash=sha512:326574c7542110d2cd8071136a36a6cffc7637ba948b55e0abb7f30f3821843073223301ecbec1d48b8361b0d7ccb338725eeb0424696efedc3f6bd2a23331d3

View File

@ -0,0 +1,2 @@
# Setuptools (Allows installation of pycparser which is a sub-dependency of the cryptography and PyNaCl packages)
setuptools==41.4.0 --hash=sha512:a27b38d596931dfef81d705d05689b7748ce0e02d21af4a37204fc74b0913fa7241b8135535eb7749f09af361cad90c475af98493fef11c4ad974780ee01243d # Tails4: 40.8.0 OnionShare2: -

View File

@ -1,2 +1,2 @@
# Virtual environment (Used to create an isolated Python environment for TFC dependencies)
virtualenv==16.7.3 --hash=sha512:760587ac587609607526d20d62c5ef2d768d4bc2dc1f7d5ce338d3525ec49cdb60782311dfd4b814defc486292e181a802f561508980f4eb332366355c5e8cb1
virtualenv==16.7.7 --hash=sha512:e80eb04615d1dcd2546bd5ceef5408bbb577fa0dd725bc69f20dd7840518af575f0b41e629e8164fdaea398628813720a6f70a42e7748336601391605b79f542

View File

@ -5,24 +5,23 @@ pyserial==3.4 --hash=sha512:8333ac2843fd136d5d0d63b527b37866f7d18afc3bb33c
# Argon2 (Derives keys that protect persistent user data)
argon2_cffi==19.1.0 --hash=sha512:77b17303a5d22fc35ac4771be5c710627c80ed7d6bf6705f70015197dbbc2b699ad6af0604b4517d1afd2f6d153058150a5d2933d38e4b4ca741e4ac560ddf72
cffi==1.12.3 --hash=sha512:69a2d725395a1a3585556cb44b62c49bd7f88f41ff194b60d4b9b591c4878a907c0770ef4052b588eaa9d420a53cbeb6b13237fff4054bf26ba5deaa84e25afa
cffi==1.13.1 --hash=sha512:fdefd3f63f56adff50723d6a88dc6db816d3d8a31b563599d2a3633ba796f6f70d5a9430510852b3d62b97357f8764f17eeab74b13df16c7cc34e1671a82373b
pycparser==2.19 --hash=sha512:7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5
six==1.12.0 --hash=sha512:326574c7542110d2cd8071136a36a6cffc7637ba948b55e0abb7f30f3821843073223301ecbec1d48b8361b0d7ccb338725eeb0424696efedc3f6bd2a23331d3
# PyNaCl (Handles TCB-side XChaCha20-Poly1305 symmetric encryption)
PyNaCl==1.3.0 --hash=sha512:c4017c38b026a5c531b15839b8d61d1fae9907ba1960c2f97f4cd67fe0827729346d5186a6d6927ba84f64b4cbfdece12b287aa7750a039f4160831be871cea3
setuptools==41.2.0 --hash=sha512:125341f0c22e11d2bd24c453b22e8fd7fd71605ee7a44eb61228686326eaca2e8f35b7ad4d0eacde4865f4d8cb8acb5cb5e3ff2856e756632b71af2f0dbdbee9
setuptools==41.4.0 --hash=sha512:a27b38d596931dfef81d705d05689b7748ce0e02d21af4a37204fc74b0913fa7241b8135535eb7749f09af361cad90c475af98493fef11c4ad974780ee01243d
# Duplicate sub-dependencies
# cffi==1.12.3 --hash=sha512:69a2d725395a1a3585556cb44b62c49bd7f88f41ff194b60d4b9b591c4878a907c0770ef4052b588eaa9d420a53cbeb6b13237fff4054bf26ba5deaa84e25afa
# cffi==1.13.1 --hash=sha512:fdefd3f63f56adff50723d6a88dc6db816d3d8a31b563599d2a3633ba796f6f70d5a9430510852b3d62b97357f8764f17eeab74b13df16c7cc34e1671a82373b
# pycparser==2.19 --hash=sha512:7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5
# six==1.12.0 --hash=sha512:326574c7542110d2cd8071136a36a6cffc7637ba948b55e0abb7f30f3821843073223301ecbec1d48b8361b0d7ccb338725eeb0424696efedc3f6bd2a23331d3
# Cryptography (Handles TCB-side X448 key exchange)
cryptography==2.7 --hash=sha512:1285c3f5181da41bace4f9fd5ce5fc4bfba71143b39a4f3d8bab642db65bec9556b1965b1c2990236fed9d6b156bf81e6c0642d1531eadf7b92379c25cc4aeac
asn1crypto==0.24.0 --hash=sha512:8d9bc344981079ac6c00e71e161c34b6f403e575bbfe1ad06e30a3bcb33e0db317bdcb7aed2d18d510cb1b3ee340a649f7f77a00d271fcf3cc388e6655b67533
cryptography==2.8 --hash=sha512:184003c89fee74892de25c3e5ec366faea7a5f1fcca3c82b0d5e5f9f797286671a820ca54da5266d6f879ab342c97e25bce9db366c5fb1178690cd5978d4d622
# Duplicate sub-dependencies
# cffi==1.12.3 --hash=sha512:69a2d725395a1a3585556cb44b62c49bd7f88f41ff194b60d4b9b591c4878a907c0770ef4052b588eaa9d420a53cbeb6b13237fff4054bf26ba5deaa84e25afa
# cffi==1.13.1 --hash=sha512:fdefd3f63f56adff50723d6a88dc6db816d3d8a31b563599d2a3633ba796f6f70d5a9430510852b3d62b97357f8764f17eeab74b13df16c7cc34e1671a82373b
# pycparser==2.19 --hash=sha512:7f830e1c9066ee2d297a55e2bf6db4bf6447b6d9da0145d11a88c3bb98505755fb7986eafa6e06ae0b7680838f5e5d6a6d188245ca5ad45c2a727587bac93ab5
# six==1.12.0 --hash=sha512:326574c7542110d2cd8071136a36a6cffc7637ba948b55e0abb7f30f3821843073223301ecbec1d48b8361b0d7ccb338725eeb0424696efedc3f6bd2a23331d3

View File

@ -48,9 +48,9 @@ from cryptography.hazmat.primitives.serialization import Encoding, PublicForma
from src.common.exceptions import CriticalError
from src.common.misc import separate_header
from src.common.statics import ARGON2_SALT_LENGTH, BITS_PER_BYTE, BLAKE2_DIGEST_LENGTH, BLAKE2_DIGEST_LENGTH_MAX
from src.common.statics import BLAKE2_DIGEST_LENGTH_MIN, PADDING_LENGTH, SYMMETRIC_KEY_LENGTH, TFC_PUBLIC_KEY_LENGTH
from src.common.statics import XCHACHA20_NONCE_LENGTH
from src.common.statics import (ARGON2_SALT_LENGTH, BITS_PER_BYTE, BLAKE2_DIGEST_LENGTH, BLAKE2_DIGEST_LENGTH_MAX,
BLAKE2_DIGEST_LENGTH_MIN, PADDING_LENGTH, SYMMETRIC_KEY_LENGTH,
TFC_PUBLIC_KEY_LENGTH, X448_SHARED_SECRET_LENGTH, XCHACHA20_NONCE_LENGTH)
def blake2b(message: bytes, # Message to hash
@ -118,14 +118,20 @@ def blake2b(message: bytes, # Message to hash
https://github.com/python/cpython/blob/3.7/Lib/hashlib.py
"""
try:
digest = hashlib.blake2b(message, digest_size=digest_size, key=key, salt=salt, person=person).digest() # type: bytes
if len(digest) != digest_size:
raise CriticalError(f"BLAKE2b digest had invalid length ({len(digest)} bytes).")
digest = hashlib.blake2b(message,
digest_size=digest_size,
key=key,
salt=salt,
person=person).digest() # type: bytes
except ValueError as e:
raise CriticalError(str(e))
if not isinstance(digest, bytes):
raise CriticalError(f"BLAKE2b returned an invalid type ({type(digest)}) digest.")
if len(digest) != digest_size:
raise CriticalError(f"BLAKE2b digest had invalid length ({len(digest)} bytes).")
return digest
@ -220,6 +226,12 @@ def argon2_kdf(password: str, # Password to derive the key from
except argon2.exceptions.Argon2Error as e:
raise CriticalError(str(e))
if not isinstance(key, bytes):
raise CriticalError(f"Argon2 returned an invalid type ({type(key)}) key.")
if len(key) != SYMMETRIC_KEY_LENGTH:
raise CriticalError(f"Derived an invalid length key from password ({len(key)} bytes).")
return key
@ -357,8 +369,11 @@ class X448(object):
public_key = private_key.public_key().public_bytes(encoding=Encoding.Raw,
format=PublicFormat.Raw) # type: bytes
if not isinstance(public_key, bytes):
raise CriticalError(f"Generated an invalid type ({type(public_key)}) public key.")
if len(public_key) != TFC_PUBLIC_KEY_LENGTH:
raise CriticalError(f"Generated invalid size public key from private key ({len(public_key)} bytes).")
raise CriticalError(f"Generated an invalid size public key from private key ({len(public_key)} bytes).")
return public_key
@ -392,6 +407,12 @@ class X448(object):
except ValueError as e:
raise CriticalError(str(e))
if not isinstance(shared_secret, bytes): # pragma: no cover
raise CriticalError(f"Derived an invalid type ({type(shared_secret)}) shared secret.")
if len(shared_secret) != X448_SHARED_SECRET_LENGTH: # pragma: no cover
raise CriticalError(f"Generated an invalid size shared secret ({len(shared_secret)} bytes).")
return blake2b(shared_secret, digest_size=SYMMETRIC_KEY_LENGTH)
@ -548,6 +569,9 @@ def byte_padding(bytestring: bytes # Bytestring to be padded
padded = padder.update(bytestring) # type: bytes
padded += padder.finalize()
if not isinstance(padded, bytes):
raise CriticalError(f"Padded message had invalid type ({type(padded)}).")
if len(padded) % PADDING_LENGTH != 0:
raise CriticalError(f"Padded message had an invalid length ({len(padded)}).")
@ -623,7 +647,7 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
add_device add_hwgenerator add_input add_disk add_interrupt
_randomness _randomness _randomness_randomness _randomness
[1] https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Studies/LinuxRNG/LinuxRNG_EN.pdf?__blob=publicationFile&v=16
@ -642,15 +666,15 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
o add_hwgenerator_randomness: HWRNGs supported by the Linux
kernel, if available. The output of the HWRNG device is used
to seed the ChaCha20 DRNG if needed, and then to seed the
input_pool directly when the entropy estimator's value falls
input_pool directly when the entropy estimator's value falls
below the set threshold. (CPU HWRNG is not processed by the
add_hwgenerator_randomness service function).[1; pp.52-54]
o add_input_randomness: Key presses, mouse movements, mouse
button presses etc. Repeated event values (e.g. key presses or
button presses etc. Repeated event values (e.g. key presses or
same direction mouse movements) are ignored by the service
function.[1; p.44]
The event data consists of four LSBs of the event type,
The event data consists of four LSBs of the event type,
four MSBs of the event code, the event code itself, and the
event value, all XORed together.[1; p.45]
The resulting event data is fed into the input_pool via
@ -673,7 +697,7 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
o add_interrupt_randomness: Interrupts (i.e. signals from SW/HW
to processor that an event needs immediate attention) occur
hundreds of thousands of times per second under average load.
hundreds of thousands of times per second under average load.
The interrupt timestamps and event data are mixed into
128-bit, per-CPU pool called fast_pool. When an interrupt
occurs
@ -684,14 +708,14 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
high-resolution timestamp are XORed with the second word
of the fast_pool.
* The 32 MSBs and LSBs of the 64-bit CPU instruction pointer
value are XORed with the third and fourth word of the
value are XORed with the third and fourth word of the
fast_pool. If no pointer is available, the XORed value is
instead the return address of the add_interrupt_randomness
function.
The raw entropy mixed into the fast_pool is then distributed
The raw entropy mixed into the fast_pool is then distributed
more evenly with a function called fast_mix.
The content of the fast_pool is mixed into the input_pool
once it has data about at least 64 interrupt events, and
The content of the fast_pool is mixed into the input_pool
once it has data about at least 64 interrupt events, and
(unless the ChaCha20 DRNG is being seeded) at least one second
has passed since the fast_pool was last mixed in. The counter
keeping track of the interrupt events is then zeroed.
@ -721,16 +745,16 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
- AMD: A set of 16 ring oscillator chains feeds 512 bits
of raw entropy to AES256-CBC-MAC based conditioner
again available via RDSEED instruction. The
again available via RDSEED instruction. The
conditioner is used to produce 128-bit seeds --
a process that is repeated thrice to create a
384-bit seed for the AES256-CTR based DRBG
available via the RDRAND instruction. The DRBG is
a process that is repeated thrice to create a
384-bit seed for the AES256-CTR based DRBG
available via the RDRAND instruction. The DRBG is
reseeded at least every 2048 queries of 32-bits
(8kB).[4; pp.2-3]
While the RDSEED/RDRAND instructions are used extensively,
because the CPU HWRNG is not an auditable source, it is
because the CPU HWRNG is not an auditable source, it is
assumed to provide only a very small amount of entropy.
[1; p.83]
@ -765,7 +789,7 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
Initialization of the input_pool
--------------------------------
The input_pool is initialized during boot time of the kernel by
The input_pool is initialized during boot time of the kernel by
mixing following data into the entropy pool:
1. The current time with nanosecond precision (64-bit CPUs).
2. Entropy obtained from CPU HWRNG via RDRAND instruction, if
@ -776,7 +800,7 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
Initial seeding and seeding levels of the input_pool
----------------------------------------------------
After a hardware event has occurred, the entropy of the event value
is estimated, and both values are mixed into the input_pool using a
is estimated, and both values are mixed into the input_pool using a
function based on a linear feedback shift register (LFSR), one byte
at a time.[1; p.23]
@ -805,7 +829,7 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
The "SHA-1" digest is mixed back into the input_pool using the
LFSR-based state transition function to provide backtracking
resistance.[1; p.18]
If more than 80-bits of entropy is requested, the
If more than 80-bits of entropy is requested, the
hash-fold-yield-mix-back operation is repeated until the requested
number of bytes are generated. (Reseeding the ChaCha20 DRNG requires
four consecutive requests.)[1; p.18]
@ -839,7 +863,7 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
- 4-byte counter (the counter is actually 64-bits[2])
- 12-byte nonce
In addition, the DRNG state contains a 4-byte timestamp called
In addition, the DRNG state contains a 4-byte timestamp called
init_time, that keeps track of when the DRNG was last seeded.
[1; pp.32-33]
@ -866,9 +890,9 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
input_pool the next time it is called.[1; p.35]
**Initially seeded state**
During initialization time of the kernel, the kernel injects four
sets of data from fast_pool into the DRNG (instead of the
input_pool). Each set contains event data and timestamps of 64
During initialization time of the kernel, the kernel injects four
sets of data from fast_pool into the DRNG (instead of the
input_pool). Each set contains event data and timestamps of 64
interrupt events from add_interrupt_randomness.[1; p.35]
In addition, all content from the add_device_randomness source
is mixed into the DRNG key state using an LFSR with a period of 255.
@ -920,7 +944,7 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
The ChaCha20 DRNG is reseeded automatically every 300 seconds
irrespective of the amount of data produced by the DRNG[1; p.32].
The DRNG is reseeded by obtaining 128..256 bits of entropy
from the input_pool. In the order of preference, the entropy from
from the input_pool. In the order of preference, the entropy from
the input_pool is XORed with the output of
1. 32-byte value obtained via RDSEED CPU instruction, or
2. 32-byte value obtained via RDRAND CPU instruction, or
@ -939,7 +963,7 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
GETRANDOM and Python
====================
Since Python 3.6.0, `os.urandom` has been a wrapper for the best
Since Python 3.6.0, `os.urandom` has been a wrapper for the best
available CSPRNG. The 3.17 and earlier versions of the Linux kernel
do not support the GETRANDOM call, and Python 3.7's `os.urandom`
will in those cases fall back to non-blocking `/dev/urandom` that is
@ -952,7 +976,7 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
the kernel version required by TFC is bumped to 4.8, and to make
sure the ChaCha20 DRNG is always seeded from input_pool before its
considered fully seeded, the final minimum requirement is 4.17).
The flag 0 means GETRANDOM will block if the DRNG is not fully
The flag 0 means GETRANDOM will block if the DRNG is not fully
seeded.[1]
Quoting PEP 524 [2]:
@ -1000,6 +1024,9 @@ def csprng(key_length: int = SYMMETRIC_KEY_LENGTH # Length of the key
entropy = os.getrandom(key_length, flags=0) # type: bytes
if not isinstance(entropy, bytes):
raise CriticalError(f"GETRANDOM returned invalid type data ({type(entropy)}).")
if len(entropy) != key_length:
raise CriticalError(f"GETRANDOM returned invalid amount of entropy ({len(entropy)} bytes).")

View File

@ -30,7 +30,10 @@ from src.common.encoding import bytes_to_bool, onion_address_to_pub_key, bytes
from src.common.exceptions import CriticalError
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.statics import *
from src.common.statics import (CONTACT_LENGTH, CONTACT_LIST_INDENT, DIR_USER_DATA, DUMMY_CONTACT, DUMMY_NICK, ECDHE,
ENCODED_BOOLEAN_LENGTH, FINGERPRINT_LENGTH, KEX_STATUS_HAS_RX_PSK, KEX_STATUS_LENGTH,
KEX_STATUS_NONE, KEX_STATUS_NO_RX_PSK, KEX_STATUS_PENDING, KEX_STATUS_UNVERIFIED,
KEX_STATUS_VERIFIED, LOCAL_ID, ONION_SERVICE_PUBLIC_KEY_LENGTH, PSK)
if typing.TYPE_CHECKING:
from src.common.db_masterkey import MasterKey

View File

@ -32,7 +32,10 @@ from src.common.encoding import bytes_to_bool, bytes_to_int, bytes_to_str
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 split_byte_string
from src.common.statics import *
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,
GROUP_ID_LENGTH, GROUP_STATIC_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH,
PADDED_UTF32_STR_LENGTH)
if typing.TYPE_CHECKING:
from src.common.db_contacts import ContactList

View File

@ -29,7 +29,10 @@ from src.common.encoding import 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.misc import ensure_dir, separate_headers, split_byte_string
from src.common.statics import *
from src.common.statics import (DIR_USER_DATA, DUMMY_CONTACT, HARAC_LENGTH, INITIAL_HARAC, KDB_ADD_ENTRY_HEADER,
KDB_CHANGE_MASTER_KEY_HEADER, KDB_REMOVE_ENTRY_HEADER, KDB_UPDATE_SIZE_HEADER,
KEYSET_LENGTH, LOCAL_PUBKEY, ONION_SERVICE_PUBLIC_KEY_LENGTH, RX,
SYMMETRIC_KEY_LENGTH, TX)
if typing.TYPE_CHECKING:
from src.common.db_masterkey import MasterKey

View File

@ -34,7 +34,12 @@ from src.common.encoding import b58encode, bytes_to_bool, bytes_to_timestamp,
from src.common.exceptions import CriticalError, FunctionReturn
from src.common.misc import ensure_dir, get_terminal_width, ignored, separate_header, separate_headers
from src.common.output import clear_screen
from src.common.statics import *
from src.common.statics import (ASSEMBLY_PACKET_HEADER_LENGTH, DIR_USER_DATA, GROUP_ID_LENGTH, GROUP_MESSAGE_HEADER,
GROUP_MSG_ID_LENGTH, LOGFILE_MASKING_QUEUE, LOG_ENTRY_LENGTH, LOG_PACKET_QUEUE,
LOG_SETTING_QUEUE, MESSAGE, MESSAGE_HEADER_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH,
ORIGIN_HEADER_LENGTH, ORIGIN_USER_HEADER, PLACEHOLDER_DATA, PRIVATE_MESSAGE_HEADER,
P_N_HEADER, RX, TIMESTAMP_LENGTH, TRAFFIC_MASKING_QUEUE, TX, UNIT_TEST_QUEUE,
WHISPER_FIELD_LENGTH, WIN_TYPE_CONTACT, WIN_TYPE_GROUP)
from src.receiver.packet import PacketList
from src.receiver.windows import RxWindow
@ -52,7 +57,7 @@ MsgTuple = Tuple[datetime, str, bytes, bytes, bool, bool]
def log_writer_loop(queues: Dict[bytes, 'Queue[Any]'], # Dictionary of queues
settings: 'Settings', # Settings object
unit_test: bool = False # When True, exits the 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:
"""Write assembly packets to log database.
@ -299,7 +304,7 @@ def change_log_db_key(previous_key: bytes,
temp_name = f'{file_name}_temp'
if not os.path.isfile(file_name):
raise FunctionReturn("Error: Could not find log database.")
raise FunctionReturn("No log database available.")
if os.path.isfile(temp_name):
os.remove(temp_name)

View File

@ -19,11 +19,13 @@ You should have received a copy of the GNU General Public License
along with TFC. If not, see <https://www.gnu.org/licenses/>.
"""
import math
import multiprocessing
import os.path
import random
import time
from typing import Tuple
from typing import List, Tuple
from src.common.crypto import argon2_kdf, blake2b, csprng
from src.common.encoding import bytes_to_int, int_to_bytes
@ -31,7 +33,11 @@ from src.common.exceptions import CriticalError, graceful_exit
from src.common.input import pwd_prompt
from src.common.misc import ensure_dir, separate_headers
from src.common.output import clear_screen, m_print, phase, print_on_previous_line
from src.common.statics import *
from src.common.word_list import eff_wordlist
from src.common.statics import (ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM, ARGON2_MIN_TIME_COST,
ARGON2_SALT_LENGTH, BLAKE2_DIGEST_LENGTH, DIR_USER_DATA, DONE,
ENCODED_INTEGER_LENGTH, GENERATE, MASTERKEY_DB_SIZE, MAX_KEY_DERIVATION_TIME,
MIN_KEY_DERIVATION_TIME, PASSWORD_MIN_BIT_STRENGTH, RESET)
class MasterKey(object):
@ -69,13 +75,30 @@ class MasterKey(object):
return master_key, kd_time
@staticmethod
def get_free_memory() -> int:
"""Return the amount of free memory in the system."""
fields = os.popen("cat /proc/meminfo").read().splitlines()
field = [f for f in fields if f.startswith('MemFree')][0]
mem_free = int(field.split()[1])
def get_available_memory() -> int:
"""Return the amount of available memory in the system."""
fields = os.popen("cat /proc/meminfo").read().splitlines()
field = [f for f in fields if f.startswith('MemAvailable')][0]
mem_avail = int(field.split()[1])
return mem_free
return mem_avail
@staticmethod
def generate_master_password() -> Tuple[int, str]:
"""Generate a strong password using the EFF wordlist."""
word_space = len(eff_wordlist)
sys_rand = random.SystemRandom()
pwd_bit_strength = 0.0
password_words = [] # type: List[str]
while pwd_bit_strength < PASSWORD_MIN_BIT_STRENGTH:
password_words.append(sys_rand.choice(eff_wordlist))
pwd_bit_strength = math.log2(word_space ** len(password_words))
password = ' '.join(password_words)
return int(pwd_bit_strength), password
def new_master_key(self) -> bytes:
"""Create a new master key from password and salt.
@ -143,7 +166,7 @@ class MasterKey(object):
time_cost = ARGON2_MIN_TIME_COST
# Determine the amount of memory used from the amount of free RAM in the system.
memory_cost = self.get_free_memory()
memory_cost = self.get_available_memory()
if self.local_test:
memory_cost //= 2
@ -180,8 +203,8 @@ class MasterKey(object):
middle = (lower_bound + upper_bound) // 2
master_key, kd_time = self.timed_key_derivation(password, salt, time_cost, middle, parallelism)
# End of search might happen e.g. if external CPU load causes delay in key derivation, which causes
# the search to continue into wrong branch. In such situation the search is restarted. The binary search
# 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)
@ -251,7 +274,19 @@ class MasterKey(object):
def new_password(cls, purpose: str = "master password") -> str:
"""Prompt the user to enter and confirm a new password."""
password_1 = pwd_prompt(f"Enter a new {purpose}: ")
password_2 = pwd_prompt(f"Confirm the {purpose}: ", repeat=True)
if password_1 == GENERATE:
pwd_bit_strength, password_1 = MasterKey.generate_master_password()
m_print([f"Generated a {pwd_bit_strength}-bit password:",
'', password_1, '',
"Write down this password and dispose of the copy once you remember it.",
"Press <Enter> to continue."], manual_proceed=True, box=True, head=1, tail=1)
os.system(RESET)
password_2 = password_1
else:
password_2 = pwd_prompt(f"Confirm the {purpose}: ", repeat=True)
if password_1 == password_2:
return password_1

View File

@ -29,7 +29,7 @@ from src.common.encoding import pub_key_to_onion_address, pub_key_to_short_add
from src.common.exceptions import CriticalError
from src.common.misc import ensure_dir
from src.common.output import phase
from src.common.statics import *
from src.common.statics import CONFIRM_CODE_LENGTH, DIR_USER_DATA, DONE, ONION_SERVICE_PRIVATE_KEY_LENGTH, TX
if typing.TYPE_CHECKING:
from src.common.db_masterkey import MasterKey

View File

@ -32,7 +32,9 @@ from src.common.exceptions import CriticalError, FunctionReturn
from src.common.input import yes
from src.common.misc import ensure_dir, get_terminal_width, round_up
from src.common.output import clear_screen, m_print
from src.common.statics import *
from src.common.statics import (DIR_USER_DATA, ENCODED_BOOLEAN_LENGTH, ENCODED_FLOAT_LENGTH, ENCODED_INTEGER_LENGTH,
MAX_INT, SETTINGS_INDENT, TRAFFIC_MASKING_MIN_RANDOM_DELAY,
TRAFFIC_MASKING_MIN_STATIC_DELAY, TX)
if typing.TYPE_CHECKING:
from src.common.db_contacts import ContactList
@ -183,7 +185,7 @@ class Settings(object):
raise CriticalError("Invalid attribute type in settings.")
except (KeyError, ValueError):
raise FunctionReturn(f"Error: Invalid value '{value_str}'.", head_clear=True)
raise FunctionReturn(f"Error: Invalid setting value '{value_str}'.", head_clear=True)
self.validate_key_value_pair(key, value, contact_list, group_list)

View File

@ -26,7 +26,9 @@ import struct
from datetime import datetime
from typing import List, Union
from src.common.statics import *
from src.common.statics import (B58_ALPHABET, B58_CHECKSUM_LENGTH, MAINNET_HEADER, ONION_ADDRESS_CHECKSUM_ID,
ONION_ADDRESS_CHECKSUM_LENGTH, ONION_SERVICE_VERSION, ONION_SERVICE_VERSION_LENGTH,
PADDING_LENGTH, TESTNET_HEADER, TRUNC_ADDRESS_LENGTH)
def sha256d(message: bytes) -> bytes:
@ -43,11 +45,7 @@ def b58encode(byte_string: bytes, public_key: bool = False) -> str:
(WIF) for mainnet and testnet addresses.
https://en.bitcoin.it/wiki/Wallet_import_format
"""
mainnet_header = b'\x80'
testnet_header = b'\xef'
net_id = testnet_header if public_key else mainnet_header
net_id = TESTNET_HEADER if public_key else MAINNET_HEADER
byte_string = net_id + byte_string
byte_string += sha256d(byte_string)[:B58_CHECKSUM_LENGTH]
@ -70,11 +68,7 @@ def b58encode(byte_string: bytes, public_key: bool = False) -> str:
def b58decode(string: str, public_key: bool = False) -> bytes:
"""Decode a Base58-encoded string and verify the checksum."""
mainnet_header = b'\x80'
testnet_header = b'\xef'
net_id = testnet_header if public_key else mainnet_header
net_id = TESTNET_HEADER if public_key else MAINNET_HEADER
orig_len = len(string)
string = string.lstrip(B58_ALPHABET[0])
new_len = len(string)
@ -129,10 +123,10 @@ def b10encode(fingerprint: bytes) -> str:
(fingerprints are usually read aloud over off band call).
Base10 has 41% efficiency but natural languages have evolved in a
way that makes a clear distinction between the way different numbers
are pronounced: reading them is faster and less error-prone.
Compliments to Signal/WA developers for discovering this.
https://signal.org/blog/safety-number-updates/
way that makes a clear distinction between the way different
numbers are pronounced: reading them is faster and less
error-prone. Compliments to Signal/WA developers for
discovering this: https://signal.org/blog/safety-number-updates/
"""
return str(int(fingerprint.hex(), base=16))

View File

@ -27,7 +27,7 @@ from datetime import datetime
from typing import Optional
from src.common.output import clear_screen, m_print
from src.common.statics import *
from src.common.statics import TFC
if typing.TYPE_CHECKING:
from src.receiver.windows import RxWindow

View File

@ -31,7 +31,7 @@ import time
import typing
from datetime import datetime
from typing import Any, Dict, Optional, Tuple, Union
from typing import Dict, Optional, Tuple, Union
from serial.serialutil import SerialException
@ -41,13 +41,16 @@ from src.common.misc import calculate_race_condition_delay, ensure_dir,
from src.common.misc import separate_trailer
from src.common.output import m_print, phase, print_on_previous_line
from src.common.reed_solomon import ReedSolomonError, RSCodec
from src.common.statics import *
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,
PACKET_CHECKSUM_LENGTH, RECEIVER, RELAY, RP_LISTEN_SOCKET, RX,
SERIAL_RX_MIN_TIMEOUT, SETTINGS_INDENT, SRC_DD_LISTEN_SOCKET, TRANSMITTER, TX)
if typing.TYPE_CHECKING:
from multiprocessing import Queue
def gateway_loop(queues: Dict[bytes, 'Queue[Any]'],
def gateway_loop(queues: Dict[bytes, 'Queue[Tuple[datetime, bytes]]'],
gateway: 'Gateway',
unit_test: bool = False
) -> None:
@ -131,9 +134,9 @@ class Gateway(object):
the time it takes to send one byte with given baud rate.
"""
try:
serial_interface = self.search_serial_interface()
baudrate = self.settings.session_serial_baudrate
self.tx_serial = self.rx_serial = serial.Serial(serial_interface, baudrate, timeout=0)
self.tx_serial = self.rx_serial = serial.Serial(self.search_serial_interface(),
self.settings.session_serial_baudrate,
timeout=0)
except SerialException:
raise CriticalError("SerialException. Ensure $USER is in the dialout group by restarting this computer.")
@ -509,7 +512,7 @@ class GatewaySettings(object):
raise CriticalError("Invalid attribute type in settings.")
except (KeyError, ValueError):
raise FunctionReturn(f"Error: Invalid value '{value_str}'.", delay=1, tail_clear=True)
raise FunctionReturn(f"Error: Invalid setting value '{value_str}'.", delay=1, tail_clear=True)
self.validate_key_value_pair(key, value)

View File

@ -28,7 +28,8 @@ from src.common.encoding import b58decode
from src.common.exceptions import CriticalError
from src.common.misc import get_terminal_width, terminal_width_check
from src.common.output import clear_screen, m_print, print_on_previous_line, print_spacing
from src.common.statics import *
from src.common.statics import (B58_LOCAL_KEY, B58_LOCAL_KEY_GUIDE, B58_PUBLIC_KEY, B58_PUBLIC_KEY_GUIDE,
CURSOR_UP_ONE_LINE, ECDHE, NC_BYPASS_START, NC_BYPASS_STOP)
if typing.TYPE_CHECKING:
from src.common.db_settings import Settings

View File

@ -34,12 +34,17 @@ import zlib
from contextlib import contextmanager
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Type, Union
from multiprocessing import Process, Queue
from multiprocessing import Process
from src.common.reed_solomon import RSCodec
from src.common.statics import *
from src.common.statics import (BAUDS_PER_BYTE, COMMAND_LENGTH, CURSOR_UP_ONE_LINE, DIR_RECV_FILES, DIR_USER_DATA,
DUMMY_CONTACT, DUMMY_GROUP, DUMMY_MEMBER, ECDHE, EVENT, EXIT, EXIT_QUEUE, LOCAL_ID,
LOCAL_PUBKEY, ME, ONION_ADDRESS_CHECKSUM_ID, ONION_ADDRESS_CHECKSUM_LENGTH,
ONION_ADDRESS_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH, PACKET_LENGTH,
PADDING_LENGTH, POWEROFF, PSK, RX, TAILS, TX, WIPE)
if typing.TYPE_CHECKING:
from multiprocessing import Queue
from src.common.db_contacts import ContactList
from src.common.db_groups import GroupList
from src.common.db_settings import Settings
@ -164,7 +169,7 @@ def ignored(*exceptions: Type[BaseException]) -> Iterator[Any]:
def monitor_processes(process_list: List[Process],
software_operation: str,
queues: Dict[bytes, 'Queue[Any]'],
queues: Dict[bytes, 'Queue[bytes]'],
error_exit_code: int = 1
) -> None:
"""Monitor the status of `process_list` and EXIT_QUEUE.
@ -195,7 +200,9 @@ def monitor_processes(process_list: List[Process],
sys.exit(0)
if command == WIPE:
if TAILS not in subprocess.check_output('lsb_release -a', shell=True):
with open('/etc/os-release') as f:
data = f.read()
if TAILS not in data:
if software_operation == RX:
subprocess.Popen("find {} -type f -exec shred -n 3 -z -u {{}} \\;"
.format(DIR_RECV_FILES), shell=True).wait()

View File

@ -29,7 +29,10 @@ from typing import List, Optional, Union
from src.common.encoding import b10encode, b58encode, pub_key_to_onion_address
from src.common.misc import get_terminal_width, split_string
from src.common.statics import *
from src.common.statics import (ADDED_MEMBERS, ALREADY_MEMBER, B58_LOCAL_KEY_GUIDE, B58_PUBLIC_KEY_GUIDE, BOLD_ON,
CLEAR_ENTIRE_LINE, CLEAR_ENTIRE_SCREEN, CURSOR_LEFT_UP_CORNER, CURSOR_UP_ONE_LINE,
DONE, NC, NEW_GROUP, NORMAL_TEXT, NOT_IN_GROUP, RECEIVER, RELAY, REMOVED_MEMBERS, RX,
TFC, TRANSMITTER, TX, UNKNOWN_ACCOUNTS, VERSION)
if typing.TYPE_CHECKING:
from src.common.db_contacts import ContactList
@ -205,9 +208,9 @@ def print_key(message: str, # Instructive messag
if settings.local_testing_mode:
m_print([message, b58key], box=True)
else:
guide, chunk_len = (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)
key = ' '.join(split_string(b58key, item_len=chunk_len))
key = ' '.join(split_string(b58key, item_len=chunk_length))
m_print([message, guide, key], box=True)

View File

@ -197,11 +197,11 @@ class ReedSolomonError(Exception):
"""
For efficiency, gf_exp[] has size 2*GF_SIZE, so that a simple
multiplication of two numbers can be resolved without calling % 255.
For more info on how to generate this extended exponentiation table,
see paper:
"Fast software implementation of finite field operations",
For efficiency, gf_exp[] has size 2*GF_SIZE, so that a simple
multiplication of two numbers can be resolved without calling % 255.
For more info on how to generate this extended exponentiation table,
see paper:
"Fast software implementation of finite field operations",
Cheng Huang and Lihao Xu
Washington University in St. Louis, Tech. Rep (2003).
"""
@ -1568,12 +1568,12 @@ class RSCodec(object):
"""
def __init__(self,
nsym: int = 10,
nsize: int = 255,
fcr: int = 0,
prim: int = 0x11d,
generator: int = 2,
c_exp: int = 8,
nsym: int = 10,
nsize: int = 255,
fcr: int = 0,
prim: int = 0x11d,
generator: int = 2,
c_exp: int = 8,
single_gen: bool = True
) -> None:
"""\

View File

@ -21,7 +21,7 @@ along with TFC. If not, see <https://www.gnu.org/licenses/>.
"""Program details"""
TFC = 'TFC'
VERSION = '1.19.08'
VERSION = '1.19.10'
TRANSMITTER = 'Transmitter'
RECEIVER = 'Receiver'
RELAY = 'Relay'
@ -41,7 +41,7 @@ DUMMY_GROUP = 'dummy_group'
TX = 'tx'
RX = 'rx'
NC = 'nc'
TAILS = b'Tails'
TAILS = 'TAILS_PRODUCT_NAME="Tails"'
"""Window identifiers"""
@ -71,8 +71,10 @@ NOT_IN_GROUP = 'not_in_group'
UNKNOWN_ACCOUNTS = 'unknown_accounts'
"""Base58 alphabet"""
B58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
"""Base58 encoding"""
B58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
MAINNET_HEADER = b'\x80'
TESTNET_HEADER = b'\xef'
"""Base58 key types"""
@ -81,7 +83,7 @@ B58_LOCAL_KEY = 'b58_local_key'
"""Base58 key input guides"""
B58_PUBLIC_KEY_GUIDE = ' A B C D E F H H I J K L '
B58_PUBLIC_KEY_GUIDE = ' A B C D E F G H I J K L '
B58_LOCAL_KEY_GUIDE = ' A B C D E F G H I J K L M N O P Q '
@ -99,7 +101,9 @@ NOTIFY = 'notify'
"""Command identifiers"""
CLEAR = 'clear'
RESET = 'reset'
POWEROFF = 'poweroff'
POWEROFF = 'systemctl poweroff'
GENERATE = 'generate'
"""Contact setting management"""
CONTACT_SETTING_HEADER_LENGTH = 2
@ -130,7 +134,7 @@ NCDCRL = 'ncdcrl'
"""VT100 codes
VT100 codes are used to control printing to the terminal. These make
VT100 codes are used to control printing to the terminal. These make
building functions like textbox drawers possible.
"""
CURSOR_UP_ONE_LINE = '\x1b[1A'
@ -144,7 +148,7 @@ NORMAL_TEXT = '\033[0m'
"""Separators
Separator byte is a non-printable byte used to separate fields in
Separator byte is a non-printable byte used to separate fields in
serialized data structures.
"""
US_BYTE = b'\x1f'
@ -157,44 +161,44 @@ serial or over the network. They tell the receiving device what type of
datagram is in question.
Datagrams with local key header contain the encrypted local key, used to
encrypt commands and data transferred between local Source and
Destination computers. Packets with the header are only accepted by the
Relay Program when they originate from the user's Source Computer. Even
if the Networked Computer is compromised and the local key datagram is
injected to the Destination Computer, the injected key could not be
encrypt commands and data transferred between local Source and
Destination computers. Packets with the header are only accepted by the
Relay Program when they originate from the user's Source Computer. Even
if the Networked Computer is compromised and the local key datagram is
injected to the Destination Computer, the injected key could not be
accepted by the user as they don't know the decryption key for it. The
worst case scenario is a DoS attack where the Receiver Program receives
new local keys continuously. Such an attack would, however, reveal the
user they are under a sophisticated attack, and that their Networked
worst case scenario is a DoS attack where the Receiver Program receives
new local keys continuously. Such an attack would, however, reveal the
user they are under a sophisticated attack, and that their Networked
Computer has been compromised.
Datagrams with Public key header contain TCB-level public keys that
Datagrams with Public key header contain TCB-level public keys that
originate from the sender's Source Computer, and are displayed by the
recipient's Networked Computer, from where they are manually typed to
recipient's Destination Computer.
Message and command type datagrams tell the Receiver Program whether to
parse the trailing fields that determine which XChaCha20-Poly1305
decryption keys it should load. Contacts can of course try to alter
their datagrams to contain a COMMAND_DATAGRAM_HEADER header, but Relay
Program will by design drop them. Even if a compromised Networked
Computer injects such a datagram to Destination Computer, the Receiver
Program will drop the datagram when the MAC verification of the
Message and command type datagrams tell the Receiver Program whether to
parse the trailing fields that determine which XChaCha20-Poly1305
decryption keys it should load. Contacts can of course try to alter
their datagrams to contain a COMMAND_DATAGRAM_HEADER header, but Relay
Program will by design drop them. Even if a compromised Networked
Computer injects such a datagram to Destination Computer, the Receiver
Program will drop the datagram when the MAC verification of the
encrypted hash ratchet counter value fails.
File type datagram contains an encrypted file that the Receiver Program
caches until its decryption key arrives from the sender inside a
File type datagram contains an encrypted file that the Receiver Program
caches until its decryption key arrives from the sender inside a
special, automated key delivery message.
Unencrypted type datagrams contain commands intended for the Relay
Program. These commands are in some cases preceded by an encrypted
version of the command, that the Relay Program forwards to Receiver
Program on Destination Computer. The unencrypted Relay commands are
disabled during traffic masking to hide the quantity and schedule of
communication even from the Networked Computer (in case it's compromised
and monitoring the user). The fact these commands are unencrypted, do
not cause security issues because if an adversary can compromise the
Networked Computer to the point it can issue commands to the Relay
Unencrypted type datagrams contain commands intended for the Relay
Program. These commands are in some cases preceded by an encrypted
version of the command, that the Relay Program forwards to Receiver
Program on Destination Computer. The unencrypted Relay commands are
disabled during traffic masking to hide the quantity and schedule of
communication even from the Networked Computer (in case it's compromised
and monitoring the user). The fact these commands are unencrypted, do
not cause security issues because if an adversary can compromise the
Networked Computer to the point it can issue commands to the Relay
Program, they could DoS the Relay Program, and thus TFC, anyway.
"""
DATAGRAM_TIMESTAMP_LENGTH = 8
@ -209,9 +213,9 @@ UNENCRYPTED_DATAGRAM_HEADER = b'U'
"""Group management headers
Group management datagrams are are automatic messages that the
Transmitter Program recommends the user to send when they make changes
to the member list of a group, or when they add or remove groups. These
Group management datagrams are are automatic messages that the
Transmitter Program recommends the user to send when they make changes
to the member list of a group, or when they add or remove groups. These
messages are displayed by the Relay Program.
"""
GROUP_ID_LENGTH = 4
@ -227,10 +231,10 @@ GROUP_MSG_EXIT_GROUP_HEADER = b'X'
"""Assembly packet headers
These one-byte assembly packet headers are not part of the padded
These one-byte assembly packet headers are not part of the padded
message parsed from assembly packets. They are however the very first
plaintext byte, prepended to every padded assembly packet that is
delivered to the recipient/local Destination Computer. The header
plaintext byte, prepended to every padded assembly packet that is
delivered to the recipient/local Destination Computer. The header
delivers the information about if and when to assemble the packet,
as well as when to drop any previously collected assembly packets.
"""
@ -260,12 +264,12 @@ C_N_HEADER = b'5' # Noise command packet
"""Unencrypted command headers
These two-byte headers are only used to control the Relay Program on
Networked Computer. These commands will not be used during traffic
These two-byte headers are only used to control the Relay Program on
Networked Computer. These commands will not be used during traffic
masking, as they would reveal when TFC is being used. These commands do
not require encryption, because if an attacker can compromise the
Networked Computer to the point it could inject commands to Relay
Program, it could most likely also access any decryption keys used by
not require encryption, because if an attacker can compromise the
Networked Computer to the point it could inject commands to Relay
Program, it could most likely also access any decryption keys used by
the Relay Program.
"""
UNENCRYPTED_COMMAND_HEADER_LENGTH = 2
@ -285,10 +289,10 @@ UNENCRYPTED_MANAGE_CONTACT_REQ = b'UM'
"""Encrypted command headers
These two-byte headers determine the type of command for Receiver
Program on local Destination Computer. The header is evaluated after the
Receiver Program has received all assembly packets and assembled the
command. These headers tell the Receiver Program to which function the
These two-byte headers determine the type of command for Receiver
Program on local Destination Computer. The header is evaluated after the
Receiver Program has received all assembly packets and assembled the
command. These headers tell the Receiver Program to which function the
provided parameters (if any) must be redirected.
"""
ENCRYPTED_COMMAND_HEADER_LENGTH = 2
@ -322,17 +326,17 @@ WIPE_USR_DATA = b'WD'
"""Origin headers
This one-byte header tells the Relay and Receiver Programs whether the
This one-byte header tells the Relay and Receiver Programs whether the
account included in the packet is the source or the destination of the
transmission. The user origin header is used when the Relay Program
forwards the message packets from user's Source Computer to user's
Destination Computer. The contact origin header is used when the program
forwards packets that are loaded from servers of contacts to the user's
Destination Computer.
transmission. The user origin header is used when the Relay Program
forwards the message packets from user's Source Computer to user's
Destination Computer. The contact origin header is used when the program
forwards packets that are loaded from servers of contacts to the user's
Destination Computer.
On Destination Computer, the Receiver Program uses the origin header to
determine which unidirectional keys it should load to decrypt the
datagram payload.
determine which unidirectional keys it should load to decrypt the
datagram payload.
"""
ORIGIN_HEADER_LENGTH = 1
ORIGIN_USER_HEADER = b'o'
@ -341,25 +345,25 @@ ORIGIN_CONTACT_HEADER = b'i'
"""Message headers
This one-byte header will be prepended to each plaintext message before
padding and splitting the message. It will be evaluated once the Relay
This one-byte header will be prepended to each plaintext message before
padding and splitting the message. It will be evaluated once the Relay
Program has received all assembly packets and assembled the message.
The private and group message headers allow the Receiver Program to
determine whether the message should be displayed in a private or in a
group window. This does not allow re-direction of messages to
unauthorized group windows, because TFC's manually managed group
configuration is also a whitelist for accounts that are authorized to
The private and group message headers allow the Receiver Program to
determine whether the message should be displayed in a private or in a
group window. This does not allow re-direction of messages to
unauthorized group windows, because TFC's manually managed group
configuration is also a whitelist for accounts that are authorized to
display messages under the group's window.
Messages with the whisper message header have "sender-based control".
Unless the contact maliciously alters their Receiver Program's behavior,
Messages with the whisper message header have "sender-based control".
Unless the contact maliciously alters their Receiver Program's behavior,
whispered messages are not logged regardless of in-program controlled
settings.
Messages with file key header contain the hash of the file ciphertext
that was sent to the user earlier. It also contains the symmetric
decryption key for that file.
Messages with file key header contain the hash of the file ciphertext
that was sent to the user earlier. It also contains the symmetric
decryption key for that file.
"""
MESSAGE_HEADER_LENGTH = 1
WHISPER_FIELD_LENGTH = 1
@ -370,14 +374,14 @@ FILE_KEY_HEADER = b'k'
"""Delays
Traffic masking packet queue check delay ensures that the lookup time
Traffic masking packet queue check delay ensures that the lookup time
for the packet queue is obfuscated.
The local testing packet delay is an arbitrary delay that simulates the
The local testing packet delay is an arbitrary delay that simulates the
slight delay caused by data transmission over a serial interface.
The Relay client delays are values that determine the delays between
checking the online status of the contact (and the state of their
checking the online status of the contact (and the state of their
ephemeral URL token public key).
"""
TRAFFIC_MASKING_QUEUE_CHECK_DELAY = 0.1
@ -514,21 +518,25 @@ BITS_PER_BYTE = 8
MAX_INT = 2 ** 64 - 1
B58_CHECKSUM_LENGTH = 4
TRUNC_ADDRESS_LENGTH = 5
TOR_CONTROL_PORT = 9051
TOR_SOCKS_PORT = 9050
# Key derivation
ARGON2_MIN_TIME_COST = 1
ARGON2_MIN_MEMORY_COST = 8
ARGON2_MIN_PARALLELISM = 1
ARGON2_SALT_LENGTH = 32
ARGON2_PSK_TIME_COST = 25
ARGON2_PSK_MEMORY_COST = 512 * 1024 # kibibytes
ARGON2_PSK_PARALLELISM = 2
MIN_KEY_DERIVATION_TIME = 3.0 # seconds
MAX_KEY_DERIVATION_TIME = 4.0 # seconds
ARGON2_MIN_TIME_COST = 1
ARGON2_MIN_MEMORY_COST = 8
ARGON2_MIN_PARALLELISM = 1
ARGON2_SALT_LENGTH = 32
ARGON2_PSK_TIME_COST = 25
ARGON2_PSK_MEMORY_COST = 512 * 1024 # kibibytes
ARGON2_PSK_PARALLELISM = 2
MIN_KEY_DERIVATION_TIME = 3.0 # seconds
MAX_KEY_DERIVATION_TIME = 4.0 # seconds
PASSWORD_MIN_BIT_STRENGTH = 128
# Cryptographic field sizes
TFC_PRIVATE_KEY_LENGTH = 56
TFC_PUBLIC_KEY_LENGTH = 56
X448_SHARED_SECRET_LENGTH = 56
FINGERPRINT_LENGTH = 32
ONION_SERVICE_PRIVATE_KEY_LENGTH = 32
ONION_SERVICE_PUBLIC_KEY_LENGTH = 32

7799
src/common/word_list.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,14 @@ from src.common.encoding import bytes_to_int, pub_key_to_short_address
from src.common.exceptions import FunctionReturn
from src.common.misc import ensure_dir, separate_header
from src.common.output import clear_screen, m_print, phase, print_on_previous_line
from src.common.statics import *
from src.common.statics import (CH_FILE_RECV, CH_LOGGING, CH_MASTER_KEY, CH_NICKNAME, CH_NOTIFY, CH_SETTING,
CLEAR_SCREEN, COMMAND, CONTACT_REM, CONTACT_SETTING_HEADER_LENGTH, DIR_USER_DATA,
DISABLE, DONE, ENABLE, ENCODED_INTEGER_LENGTH, ENCRYPTED_COMMAND_HEADER_LENGTH, EXIT,
EXIT_PROGRAM, GROUP_ADD, GROUP_CREATE, GROUP_DELETE, GROUP_REMOVE, GROUP_RENAME,
KEY_EX_ECDHE, KEY_EX_PSK_RX, KEY_EX_PSK_TX, LOCAL_KEY_RDY, LOCAL_PUBKEY, LOG_DISPLAY,
LOG_EXPORT, LOG_REMOVE, ONION_SERVICE_PUBLIC_KEY_LENGTH, ORIGIN_USER_HEADER, RESET,
RESET_SCREEN, US_BYTE, WIN_ACTIVITY, WIN_SELECT, WIN_TYPE_CONTACT, WIN_TYPE_GROUP,
WIN_UID_FILE, WIN_UID_LOCAL, WIPE, WIPE_USR_DATA)
from src.receiver.commands_g import group_add, group_create, group_delete, group_remove, group_rename
from src.receiver.key_exchanges import key_ex_ecdhe, key_ex_psk_rx, key_ex_psk_tx, local_key_rdy
@ -58,7 +65,7 @@ def process_command(ts: 'datetime',
settings: 'Settings',
master_key: 'MasterKey',
gateway: 'Gateway',
exit_queue: 'Queue[Any]'
exit_queue: 'Queue[bytes]'
) -> None:
"""Decrypt command assembly packet and process command."""
assembly_packet = decrypt_assembly_packet(assembly_ct, LOCAL_PUBKEY, ORIGIN_USER_HEADER,
@ -73,7 +80,7 @@ def process_command(ts: 'datetime',
header, cmd = separate_header(cmd_packet.assemble_command_packet(), ENCRYPTED_COMMAND_HEADER_LENGTH)
no = None
# Keyword Function to run ( Parameters )
# Keyword Function to run ( Parameters )
# --------------------------------------------------------------------------------------------------------------
d = {LOCAL_KEY_RDY: (local_key_rdy, ts, window_list, contact_list ),
WIN_ACTIVITY: (win_activity, window_list ),
@ -136,7 +143,7 @@ def reset_screen(win_uid: bytes, window_list: 'WindowList') -> None:
os.system(RESET)
def exit_tfc(exit_queue: 'Queue[Any]') -> None:
def exit_tfc(exit_queue: 'Queue[str]') -> None:
"""Exit TFC."""
exit_queue.put(EXIT)

View File

@ -25,7 +25,9 @@ from src.common.encoding import b58encode
from src.common.exceptions import FunctionReturn
from src.common.misc import separate_header, split_byte_string, validate_group_name
from src.common.output import group_management_print, m_print
from src.common.statics import *
from src.common.statics import (ADDED_MEMBERS, ALREADY_MEMBER, GROUP_ID_LENGTH, NEW_GROUP, NOT_IN_GROUP,
ONION_SERVICE_PUBLIC_KEY_LENGTH, REMOVED_MEMBERS, UNKNOWN_ACCOUNTS, US_BYTE,
WIN_UID_LOCAL)
if typing.TYPE_CHECKING:
from datetime import datetime

View File

@ -32,7 +32,8 @@ from src.common.encoding import bytes_to_str
from src.common.exceptions import FunctionReturn
from src.common.misc import decompress, ensure_dir, separate_headers, separate_trailer
from src.common.output import phase, print_on_previous_line
from src.common.statics import *
from src.common.statics import (DIR_RECV_FILES, DONE, ONION_SERVICE_PUBLIC_KEY_LENGTH, ORIGIN_HEADER_LENGTH,
PADDED_UTF32_STR_LENGTH, SYMMETRIC_KEY_LENGTH, US_BYTE)
if typing.TYPE_CHECKING:
from datetime import datetime
@ -171,10 +172,9 @@ def process_file(ts: 'datetime', # Timestamp of received_packet
if not file_name.isprintable() or not file_name or '/' in file_name:
raise FunctionReturn(f"Error: Name of file from {nick} was invalid.")
f_data = file_dc[PADDED_UTF32_STR_LENGTH:]
file_data = file_dc[PADDED_UTF32_STR_LENGTH:]
file_dir = f'{DIR_RECV_FILES}{nick}/'
final_name = store_unique(f_data, file_dir, file_name)
final_name = store_unique(file_data, file_dir, file_name)
message = f"Stored file from {nick} as '{final_name}'."
if settings.traffic_masking and window_list.active_win is not None:

View File

@ -27,7 +27,7 @@ import subprocess
import tkinter
import typing
from typing import Any, List, Tuple
from typing import List, Tuple
import nacl.exceptions
@ -39,7 +39,11 @@ from src.common.input import get_b58_key
from src.common.misc import separate_header, separate_headers
from src.common.output import m_print, phase, print_on_previous_line
from src.common.path import ask_path_gui
from src.common.statics import *
from src.common.statics import (ARGON2_PSK_MEMORY_COST, ARGON2_PSK_PARALLELISM, ARGON2_PSK_TIME_COST,
ARGON2_SALT_LENGTH, B58_LOCAL_KEY, CONFIRM_CODE_LENGTH, DONE, FINGERPRINT_LENGTH,
KEX_STATUS_HAS_RX_PSK, KEX_STATUS_LOCAL_KEY, KEX_STATUS_NONE, KEX_STATUS_NO_RX_PSK,
LOCAL_NICK, LOCAL_PUBKEY, ONION_SERVICE_PUBLIC_KEY_LENGTH, PSK_FILE_SIZE, RESET,
SYMMETRIC_KEY_LENGTH, WIN_TYPE_CONTACT, WIN_TYPE_GROUP)
if typing.TYPE_CHECKING:
from datetime import datetime
@ -60,7 +64,7 @@ def process_local_key(ts: 'datetime',
settings: 'Settings',
kdk_hashes: List[bytes],
packet_hashes: List[bytes],
l_queue: 'Queue[Any]'
l_queue: 'Queue[Tuple[datetime, bytes]]'
) -> None:
"""Decrypt local key packet and add local contact/keyset."""
bootstrap = not key_list.has_local_keyset()
@ -259,11 +263,12 @@ def key_ex_psk_tx(packet: bytes,
tx_hk=tx_hk,
rx_hk=bytes(SYMMETRIC_KEY_LENGTH))
c_code = blake2b(onion_pub_key, digest_size=CONFIRM_CODE_LENGTH)
message = f"Added Tx-side PSK for {nick} ({pub_key_to_short_address(onion_pub_key)})."
local_win = window_list.get_local_window()
local_win.add_new(ts, message)
m_print(message, bold=True, tail_clear=True, delay=1)
m_print([message, f"Confirmation code (to Transmitter): {c_code.hex()}"], box=True)
def key_ex_psk_rx(packet: bytes,

View File

@ -28,7 +28,11 @@ from src.common.db_logs import write_log_entry
from src.common.encoding import bytes_to_bool
from src.common.exceptions import FunctionReturn
from src.common.misc import separate_header, separate_headers
from src.common.statics import *
from src.common.statics import (ASSEMBLY_PACKET_HEADER_LENGTH, BLAKE2_DIGEST_LENGTH, FILE, FILE_KEY_HEADER,
GROUP_ID_LENGTH, GROUP_MESSAGE_HEADER, GROUP_MSG_ID_LENGTH, LOCAL_PUBKEY, MESSAGE,
MESSAGE_HEADER_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH, ORIGIN_CONTACT_HEADER,
ORIGIN_HEADER_LENGTH, ORIGIN_USER_HEADER, PLACEHOLDER_DATA, PRIVATE_MESSAGE_HEADER,
SYMMETRIC_KEY_LENGTH, WHISPER_FIELD_LENGTH)
from src.receiver.packet import decrypt_assembly_packet
@ -57,8 +61,8 @@ def process_message(ts: 'datetime',
"""Process received private / group message."""
local_window = window_list.get_local_window()
onion_pub_key, origin, assembly_packet_ct \
= separate_headers(assembly_packet_ct, [ONION_SERVICE_PUBLIC_KEY_LENGTH, ORIGIN_HEADER_LENGTH])
onion_pub_key, origin, assembly_packet_ct = separate_headers(assembly_packet_ct, [ONION_SERVICE_PUBLIC_KEY_LENGTH,
ORIGIN_HEADER_LENGTH])
if onion_pub_key == LOCAL_PUBKEY:
raise FunctionReturn("Warning! Received packet masqueraded as a command.", window=local_window)
@ -123,6 +127,7 @@ def process_message(ts: 'datetime',
else:
raise FunctionReturn("Error: Message from contact had an invalid header.")
# Logging
if whisper:
raise FunctionReturn("Whisper message complete.", output=False)

View File

@ -28,7 +28,9 @@ from typing import Any, Dict, List, Tuple
from src.common.exceptions import FunctionReturn
from src.common.output import clear_screen
from src.common.statics import *
from src.common.statics import (COMMAND_DATAGRAM_HEADER, EXIT_QUEUE, FILE_DATAGRAM_HEADER, LOCAL_KEY_DATAGRAM_HEADER,
MESSAGE_DATAGRAM_HEADER, ONION_SERVICE_PUBLIC_KEY_LENGTH, UNIT_TEST_QUEUE,
WIN_UID_FILE)
from src.receiver.commands import process_command
from src.receiver.files import new_file, process_file
@ -59,16 +61,16 @@ def output_loop(queues: Dict[bytes, 'Queue[Any]'],
unit_test: bool = False
) -> None:
"""Process packets in message queues according to their priority."""
l_queue = queues[LOCAL_KEY_DATAGRAM_HEADER]
m_queue = queues[MESSAGE_DATAGRAM_HEADER]
f_queue = queues[FILE_DATAGRAM_HEADER]
c_queue = queues[COMMAND_DATAGRAM_HEADER]
e_queue = queues[EXIT_QUEUE]
local_key_queue = queues[LOCAL_KEY_DATAGRAM_HEADER]
message_queue = queues[MESSAGE_DATAGRAM_HEADER]
file_queue = queues[FILE_DATAGRAM_HEADER]
command_queue = queues[COMMAND_DATAGRAM_HEADER]
exit_queue = queues[EXIT_QUEUE]
sys.stdin = os.fdopen(stdin_fd)
packet_buf = dict() # type: Dict[bytes, List[Tuple[datetime, bytes]]]
file_buf = dict() # type: Dict[bytes, Tuple[datetime, bytes]]
file_keys = dict() # type: Dict[bytes, bytes]
sys.stdin = os.fdopen(stdin_fd)
packet_buffer = dict() # type: Dict[bytes, List[Tuple[datetime, bytes]]]
file_buffer = dict() # type: Dict[bytes, Tuple[datetime, bytes]]
file_keys = dict() # type: Dict[bytes, bytes]
kdk_hashes = [] # type: List[bytes]
packet_hashes = [] # type: List[bytes]
@ -79,10 +81,10 @@ def output_loop(queues: Dict[bytes, 'Queue[Any]'],
clear_screen()
while True:
try:
if l_queue.qsize() != 0:
ts, packet = l_queue.get()
if local_key_queue.qsize() != 0:
ts, packet = local_key_queue.get()
process_local_key(ts, packet, window_list, contact_list, key_list,
settings, kdk_hashes, packet_hashes, l_queue)
settings, kdk_hashes, packet_hashes, local_key_queue)
continue
if not contact_list.has_local_contact():
@ -90,10 +92,10 @@ def output_loop(queues: Dict[bytes, 'Queue[Any]'],
continue
# Commands
if c_queue.qsize() != 0:
ts, packet = c_queue.get()
if command_queue.qsize() != 0:
ts, packet = command_queue.get()
process_command(ts, packet, window_list, packet_list, contact_list, key_list,
group_list, settings, master_key, gateway, e_queue)
group_list, settings, master_key, gateway, exit_queue)
continue
# File window refresh
@ -101,48 +103,48 @@ def output_loop(queues: Dict[bytes, 'Queue[Any]'],
window_list.active_win.redraw_file_win()
# Cached message packets
for onion_pub_key in packet_buf:
for onion_pub_key in packet_buffer:
if (contact_list.has_pub_key(onion_pub_key)
and key_list.has_rx_mk(onion_pub_key)
and packet_buf[onion_pub_key]):
ts, packet = packet_buf[onion_pub_key].pop(0)
and packet_buffer[onion_pub_key]):
ts, packet = packet_buffer[onion_pub_key].pop(0)
process_message(ts, packet, window_list, packet_list, contact_list, key_list,
group_list, settings, master_key, file_keys)
continue
# New messages
if m_queue.qsize() != 0:
ts, packet = m_queue.get()
if message_queue.qsize() != 0:
ts, packet = message_queue.get()
onion_pub_key = packet[:ONION_SERVICE_PUBLIC_KEY_LENGTH]
if contact_list.has_pub_key(onion_pub_key) and key_list.has_rx_mk(onion_pub_key):
process_message(ts, packet, window_list, packet_list, contact_list, key_list,
group_list, settings, master_key, file_keys)
else:
packet_buf.setdefault(onion_pub_key, []).append((ts, packet))
packet_buffer.setdefault(onion_pub_key, []).append((ts, packet))
continue
# Cached files
if file_buf:
for k in file_buf:
if file_buffer:
for k in file_buffer:
key_to_remove = b''
try:
if k in file_keys:
key_to_remove = k
ts_, file_ct = file_buf[k]
ts_, file_ct = file_buffer[k]
dec_key = file_keys[k]
onion_pub_key = k[:ONION_SERVICE_PUBLIC_KEY_LENGTH]
process_file(ts_, onion_pub_key, file_ct, dec_key, contact_list, window_list, settings)
finally:
if key_to_remove:
file_buf.pop(k)
file_buffer.pop(k)
file_keys.pop(k)
break
# New files
if f_queue.qsize() != 0:
ts, packet = f_queue.get()
new_file(ts, packet, file_keys, file_buf, contact_list, window_list, settings)
if file_queue.qsize() != 0:
ts, packet = file_queue.get()
new_file(ts, packet, file_keys, file_buffer, contact_list, window_list, settings)
time.sleep(0.01)

View File

@ -34,7 +34,12 @@ from src.common.exceptions import FunctionReturn
from src.common.input import yes
from src.common.misc import decompress, readable_size, separate_header, separate_headers, separate_trailer
from src.common.output import m_print
from src.common.statics import *
from src.common.statics import (ASSEMBLY_PACKET_HEADER_LENGTH, BLAKE2_DIGEST_LENGTH, COMMAND, C_A_HEADER, C_C_HEADER,
C_E_HEADER, C_L_HEADER, C_N_HEADER, C_S_HEADER, ENCODED_INTEGER_LENGTH, FILE,
F_A_HEADER, F_C_HEADER, F_E_HEADER, F_L_HEADER, F_S_HEADER, HARAC_CT_LENGTH,
HARAC_WARN_THRESHOLD, LOCAL_PUBKEY, MAX_MESSAGE_SIZE, MESSAGE, M_A_HEADER,
M_C_HEADER, M_E_HEADER, M_L_HEADER, M_S_HEADER, ORIGIN_CONTACT_HEADER,
ORIGIN_USER_HEADER, P_N_HEADER, RX, SYMMETRIC_KEY_LENGTH, TX, US_BYTE)
from src.receiver.files import process_assembled_file
@ -100,16 +105,16 @@ def decrypt_assembly_packet(packet: bytes, # Assembly packet cip
try:
harac_bytes = auth_and_decrypt(ct_harac, header_key)
except nacl.exceptions.CryptoError:
raise FunctionReturn(
f"Warning! Received {p_type} {direction} {nick} had an invalid hash ratchet MAC.", window=local_window)
raise FunctionReturn(f"Warning! Received {p_type} {direction} {nick} had an invalid hash ratchet MAC.",
window=local_window)
# Catch up with hash ratchet offset
purp_harac = bytes_to_int(harac_bytes)
stored_harac = getattr(keyset, f'{key_dir}_harac')
offset = purp_harac - stored_harac
if offset < 0:
raise FunctionReturn(
f"Warning! Received {p_type} {direction} {nick} had an expired hash ratchet counter.", window=local_window)
raise FunctionReturn(f"Warning! Received {p_type} {direction} {nick} had an expired hash ratchet counter.",
window=local_window)
process_offset(offset, origin, direction, nick, local_window)
for harac in range(stored_harac, stored_harac + offset):
@ -119,7 +124,8 @@ def decrypt_assembly_packet(packet: bytes, # Assembly packet cip
try:
assembly_packet = auth_and_decrypt(ct_assemby_packet, message_key)
except nacl.exceptions.CryptoError:
raise FunctionReturn(f"Warning! Received {p_type} {direction} {nick} had an invalid MAC.", window=local_window)
raise FunctionReturn(f"Warning! Received {p_type} {direction} {nick} had an invalid MAC.",
window=local_window)
# Update message key and harac
new_key = blake2b(message_key + int_to_bytes(stored_harac + offset), digest_size=SYMMETRIC_KEY_LENGTH)

View File

@ -30,7 +30,9 @@ from src.common.encoding import bytes_to_int
from src.common.exceptions import FunctionReturn
from src.common.misc import ignored, separate_headers
from src.common.output import m_print
from src.common.statics import *
from src.common.statics import (COMMAND_DATAGRAM_HEADER, DATAGRAM_HEADER_LENGTH, DATAGRAM_TIMESTAMP_LENGTH,
FILE_DATAGRAM_HEADER, GATEWAY_QUEUE, LOCAL_KEY_DATAGRAM_HEADER,
MESSAGE_DATAGRAM_HEADER)
if typing.TYPE_CHECKING:
from multiprocessing import Queue

View File

@ -27,11 +27,14 @@ import typing
from datetime import datetime
from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple
from src.common.encoding import pub_key_to_short_address
from src.common.encoding import b58encode, pub_key_to_onion_address, pub_key_to_short_address
from src.common.exceptions import FunctionReturn
from src.common.misc import get_terminal_width
from src.common.output import clear_screen, m_print, print_on_previous_line
from src.common.statics import *
from src.common.statics import (BOLD_ON, EVENT, FILE, FILE_TRANSFER_INDENT, GROUP_ID_LENGTH, GROUP_MSG_ID_LENGTH, ME,
NORMAL_TEXT, ONION_SERVICE_PUBLIC_KEY_LENGTH, ORIGIN_CONTACT_HEADER,
ORIGIN_USER_HEADER, WIN_TYPE_COMMAND, WIN_TYPE_CONTACT, WIN_TYPE_FILE,
WIN_TYPE_GROUP, WIN_UID_FILE, WIN_UID_LOCAL)
if typing.TYPE_CHECKING:
from src.common.db_contacts import Contact, ContactList
@ -96,7 +99,14 @@ class RxWindow(Iterable[MsgTuple]):
self.window_contacts = self.group.members
else:
raise FunctionReturn(f"Invalid window '{uid}'.")
if len(uid) == ONION_SERVICE_PUBLIC_KEY_LENGTH:
hr_uid = pub_key_to_onion_address(uid)
elif len(uid) == GROUP_ID_LENGTH:
hr_uid = b58encode(uid)
else:
hr_uid = "<unable to encode>"
raise FunctionReturn(f"Invalid window '{hr_uid}'.")
def __iter__(self) -> Iterator[MsgTuple]:
"""Iterate over window's message log."""
@ -238,7 +248,6 @@ class RxWindow(Iterable[MsgTuple]):
event_msg: bool = False # When True, uses "-!-" as message handle
) -> None:
"""Add message tuple to message log and optionally print it."""
self.update_handle_dict(onion_pub_key)
msg_tuple = (timestamp, message, onion_pub_key, origin, whisper, event_msg)

View File

@ -36,7 +36,15 @@ from src.common.encoding import b58encode, int_to_bytes, onion_address_to_pub_ke
from src.common.encoding import pub_key_to_short_address
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.statics import *
from src.common.statics import (CLIENT_OFFLINE_THRESHOLD, CONTACT_MGMT_QUEUE, CONTACT_REQ_QUEUE, C_REQ_MGMT_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,
GROUP_MSG_INVITE_HEADER, GROUP_MSG_JOIN_HEADER, GROUP_MSG_MEMBER_ADD_HEADER,
GROUP_MSG_MEMBER_REM_HEADER, GROUP_MSG_QUEUE, MESSAGE_DATAGRAM_HEADER,
ONION_SERVICE_PUBLIC_KEY_LENGTH, ORIGIN_CONTACT_HEADER, PUBLIC_KEY_DATAGRAM_HEADER,
RELAY_CLIENT_MAX_DELAY, RELAY_CLIENT_MIN_DELAY, RP_ADD_CONTACT_HEADER,
RP_REMOVE_CONTACT_HEADER, TFC_PUBLIC_KEY_LENGTH, TOR_DATA_QUEUE, UNIT_TEST_QUEUE,
URL_TOKEN_LENGTH, URL_TOKEN_QUEUE)
if typing.TYPE_CHECKING:
from src.common.gateway import Gateway
@ -205,7 +213,7 @@ def get_data_loop(onion_addr: str,
except requests.exceptions.RequestException:
return None
for line in r.iter_lines(): # Iterates over newline-separated datagrams
for line in r.iter_lines(): # Iterate over newline-separated datagrams
if not line:
continue
@ -219,14 +227,16 @@ def get_data_loop(onion_addr: str,
ts = datetime.now()
ts_bytes = int_to_bytes(int(ts.strftime('%Y%m%d%H%M%S%f')[:-4]))
# Packet type specific handling
if header == PUBLIC_KEY_DATAGRAM_HEADER:
if len(payload_bytes) == TFC_PUBLIC_KEY_LENGTH:
msg = f"Received public key from {short_addr} at {ts.strftime('%b %d - %H:%M:%S.%f')[:-4]}:"
print_key(msg, payload_bytes, gateway.settings, public_key=True)
elif header == MESSAGE_DATAGRAM_HEADER:
queues[DST_MESSAGE_QUEUE].put(header + ts_bytes + onion_pub_key
+ ORIGIN_CONTACT_HEADER + payload_bytes)
queues[DST_MESSAGE_QUEUE].put(header + ts_bytes
+ onion_pub_key + ORIGIN_CONTACT_HEADER + payload_bytes)
rp_print(f"Message from contact {short_addr}", ts)
elif header in [GROUP_MSG_INVITE_HEADER, GROUP_MSG_JOIN_HEADER,
@ -282,13 +292,13 @@ def g_msg_manager(queues: 'QueueDict',
pub_keys = split_byte_string(data, ONION_SERVICE_PUBLIC_KEY_LENGTH)
pub_key_length = ONION_SERVICE_PUBLIC_KEY_LENGTH
members = [k for k in pub_keys if len(k) == pub_key_length ]
known = [f" * {pub_key_to_onion_address(m)}" for m in members if m in existing_contacts]
unknown = [f" * {pub_key_to_onion_address(m)}" for m in members if m not in existing_contacts]
members = [k for k in pub_keys if len(k) == pub_key_length ]
known = [f" * {pub_key_to_onion_address(m)}" for m in members if m in existing_contacts]
unknown = [f" * {pub_key_to_onion_address(m)}" for m in members if m not in existing_contacts]
line_list = []
if known:
line_list.extend(["Known contacts"] + known)
line_list.extend(["Known contacts"] + known)
if unknown:
line_list.extend(["Unknown contacts"] + unknown)
@ -353,7 +363,8 @@ def c_req_manager(queues: 'QueueDict',
if show_requests:
ts_fmt = datetime.now().strftime('%b %d - %H:%M:%S.%f')[:-4]
m_print([f"{ts_fmt} - New contact request from an unknown TFC account:", purp_onion_address], box=True)
m_print([f"{ts_fmt} - New contact request from an unknown TFC account:", purp_onion_address],
box=True)
contact_requests.append(onion_pub_key)
if unit_test and queues[UNIT_TEST_QUEUE].qsize() != 0:

View File

@ -31,7 +31,16 @@ from src.common.encoding import bytes_to_bool, bytes_to_int
from src.common.exceptions import FunctionReturn
from src.common.misc import ignored, separate_header, separate_headers, split_byte_string
from src.common.output import clear_screen, m_print
from src.common.statics import *
from src.common.statics import (CONFIRM_CODE_LENGTH, CONTACT_MGMT_QUEUE, C_REQ_MGMT_QUEUE, C_REQ_STATE_QUEUE,
ENCODED_BOOLEAN_LENGTH, ENCODED_INTEGER_LENGTH, EXIT, GROUP_MGMT_QUEUE,
LOCAL_TESTING_PACKET_DELAY, MAX_INT, ONION_CLOSE_QUEUE, ONION_KEY_QUEUE,
ONION_SERVICE_PRIVATE_KEY_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH, RESET,
RP_ADD_CONTACT_HEADER, RP_REMOVE_CONTACT_HEADER, SRC_TO_RELAY_QUEUE,
UNENCRYPTED_ADD_EXISTING_CONTACT, UNENCRYPTED_ADD_NEW_CONTACT, UNENCRYPTED_BAUDRATE,
UNENCRYPTED_COMMAND_HEADER_LENGTH, UNENCRYPTED_EC_RATIO, UNENCRYPTED_EXIT_COMMAND,
UNENCRYPTED_MANAGE_CONTACT_REQ, UNENCRYPTED_ONION_SERVICE_DATA,
UNENCRYPTED_REM_CONTACT, UNENCRYPTED_SCREEN_CLEAR, UNENCRYPTED_SCREEN_RESET,
UNENCRYPTED_WIPE_COMMAND, WIPE)
if typing.TYPE_CHECKING:
from multiprocessing import Queue

View File

@ -27,22 +27,28 @@ import shlex
import socket
import tempfile
import time
import typing
from multiprocessing import Queue
from typing import Any, Dict
from typing import Any, Dict, Optional
import nacl.signing
import stem.control
import stem.process
from stem.control import Controller
from src.common.encoding import pub_key_to_onion_address
from src.common.exceptions import CriticalError
from src.common.output import m_print, rp_print
from src.common.statics import *
from src.common.statics import (EXIT, EXIT_QUEUE, ONION_CLOSE_QUEUE, ONION_KEY_QUEUE,
ONION_SERVICE_PRIVATE_KEY_LENGTH, TOR_CONTROL_PORT, TOR_DATA_QUEUE, TOR_SOCKS_PORT)
if typing.TYPE_CHECKING:
from multiprocessing import Queue
def get_available_port(min_port: int, max_port: int) -> str:
def get_available_port(min_port: int, max_port: int) -> int:
"""Find a random available port within the given range."""
with socket.socket() as temp_sock:
while True:
@ -51,7 +57,11 @@ def get_available_port(min_port: int, max_port: int) -> str:
break
except OSError:
pass
_, port = temp_sock.getsockname() # type: Any, str
_, port = temp_sock.getsockname() # type: Any, int
if Tor.platform_is_tails():
return TOR_SOCKS_PORT
return port
@ -59,11 +69,27 @@ class Tor(object):
"""Tor class manages the starting and stopping of Tor client."""
def __init__(self) -> None:
self.tor_process = None # type: Any
self.controller = None # type: Any
self.tor_process = None # type: Optional[Any]
self.controller = None # type: Optional[Controller]
@staticmethod
def platform_is_tails() -> bool:
"""Return True if Relay Program is running on Tails."""
with open('/etc/os-release') as f:
data = f.read()
return 'TAILS_PRODUCT_NAME="Tails"' in data
def connect(self, port: int) -> None:
"""Launch Tor as a subprocess.
If TFC is running on top of Tails, do not launch a separate
instance of Tor.
"""
if self.platform_is_tails():
self.controller = Controller.from_port(port=TOR_CONTROL_PORT)
self.controller.authenticate()
return None
def connect(self, port: str) -> None:
"""Launch Tor as a subprocess."""
tor_data_directory = tempfile.TemporaryDirectory()
tor_control_socket = os.path.join(tor_data_directory.name, 'control_socket')
@ -113,7 +139,7 @@ class Tor(object):
def stop(self) -> None:
"""Stop the Tor subprocess."""
if self.tor_process:
if self.tor_process is not None:
self.tor_process.terminate()
time.sleep(0.1)
if not self.tor_process.poll():
@ -174,6 +200,9 @@ def onion_service(queues: Dict[bytes, 'Queue[Any]']) -> None:
except (EOFError, KeyboardInterrupt):
return
if tor.controller is None:
raise CriticalError("No Tor controller")
try:
rp_print("Setup 75% - Launching Onion Service...", bold=True)
key_data = stem_compatible_ed25519_key_from_private_key(private_key)
@ -206,9 +235,11 @@ def onion_service(queues: Dict[bytes, 'Queue[Any]']) -> None:
if queues[ONION_CLOSE_QUEUE].qsize() > 0:
command = queues[ONION_CLOSE_QUEUE].get()
tor.controller.remove_hidden_service(response.service_id)
tor.stop()
if not tor.platform_is_tails() and command == EXIT:
tor.controller.remove_hidden_service(response.service_id)
tor.stop()
queues[EXIT_QUEUE].put(command)
time.sleep(5)
break
except (EOFError, KeyboardInterrupt):

View File

@ -31,7 +31,7 @@ from typing import Any, Dict, List, Optional
from flask import Flask, send_file
from src.common.statics import *
from src.common.statics import CONTACT_REQ_QUEUE, F_TO_FLASK_QUEUE, M_TO_FLASK_QUEUE, URL_TOKEN_QUEUE
if typing.TYPE_CHECKING:
QueueDict = Dict[bytes, Queue[Any]]

View File

@ -22,14 +22,20 @@ along with TFC. If not, see <https://www.gnu.org/licenses/>.
import time
import typing
from typing import Any, Dict, Union
from typing import Any, Dict, Tuple, Union
from src.common.encoding import bytes_to_int, pub_key_to_short_address
from src.common.encoding import int_to_bytes, b85encode
from src.common.exceptions import FunctionReturn
from src.common.misc import ignored, separate_header, split_byte_string
from src.common.output import rp_print
from src.common.statics import *
from src.common.statics import (COMMAND_DATAGRAM_HEADER, DATAGRAM_HEADER_LENGTH, DST_COMMAND_QUEUE,
DST_MESSAGE_QUEUE, ENCODED_INTEGER_LENGTH, FILE_DATAGRAM_HEADER, F_TO_FLASK_QUEUE,
GATEWAY_QUEUE, GROUP_ID_LENGTH, GROUP_MSG_EXIT_GROUP_HEADER, GROUP_MSG_INVITE_HEADER,
GROUP_MSG_JOIN_HEADER, GROUP_MSG_MEMBER_ADD_HEADER, GROUP_MSG_MEMBER_REM_HEADER,
LOCAL_KEY_DATAGRAM_HEADER, MESSAGE_DATAGRAM_HEADER, M_TO_FLASK_QUEUE,
ONION_SERVICE_PUBLIC_KEY_LENGTH, ORIGIN_USER_HEADER, PUBLIC_KEY_DATAGRAM_HEADER,
SRC_TO_RELAY_QUEUE, UNENCRYPTED_DATAGRAM_HEADER, UNIT_TEST_QUEUE)
if typing.TYPE_CHECKING:
from datetime import datetime
@ -40,7 +46,7 @@ if typing.TYPE_CHECKING:
def queue_to_flask(packet: Union[bytes, str],
onion_pub_key: bytes,
flask_queue: 'Queue[Any]',
flask_queue: 'Queue[Tuple[Union[bytes, str], bytes]]',
ts: 'datetime',
header: bytes
) -> None:
@ -122,7 +128,7 @@ def src_incoming(queues: 'QueueDict',
def process_group_management_message(ts: 'datetime',
packet: bytes,
header: bytes,
messages_to_flask: 'Queue[Any]') -> None:
messages_to_flask: 'Queue[Tuple[Union[bytes, str], bytes]]') -> None:
"""Parse and display group management message."""
header_str = header.decode()
group_id, packet = separate_header(packet, GROUP_ID_LENGTH)

View File

@ -35,7 +35,16 @@ from src.common.exceptions import FunctionReturn
from src.common.input import yes
from src.common.misc import ensure_dir, get_terminal_width, validate_onion_addr
from src.common.output import clear_screen, m_print, phase, print_on_previous_line
from src.common.statics import *
from src.common.statics import (CH_MASTER_KEY, CH_SETTING, CLEAR, CLEAR_SCREEN, COMMAND_PACKET_QUEUE, DIR_USER_DATA,
DONE, EXIT_PROGRAM, GROUP_ID_ENC_LENGTH, KDB_CHANGE_MASTER_KEY_HEADER,
KDB_UPDATE_SIZE_HEADER, KEX_STATUS_UNVERIFIED, KEX_STATUS_VERIFIED,
KEY_MANAGEMENT_QUEUE, LOCAL_TESTING_PACKET_DELAY, LOGFILE_MASKING_QUEUE, LOG_DISPLAY,
LOG_EXPORT, LOG_REMOVE, MESSAGE, ONION_ADDRESS_LENGTH, RELAY_PACKET_QUEUE, RESET,
RESET_SCREEN, RX, SENDER_MODE_QUEUE, TRAFFIC_MASKING_QUEUE, TX, UNENCRYPTED_BAUDRATE,
UNENCRYPTED_DATAGRAM_HEADER, UNENCRYPTED_EC_RATIO, UNENCRYPTED_EXIT_COMMAND,
UNENCRYPTED_MANAGE_CONTACT_REQ, UNENCRYPTED_SCREEN_CLEAR, UNENCRYPTED_SCREEN_RESET,
UNENCRYPTED_WIPE_COMMAND, US_BYTE, VERSION, WIN_ACTIVITY, WIN_SELECT, WIN_TYPE_GROUP,
WIN_UID_FILE, WIN_UID_LOCAL, WIPE_USR_DATA)
from src.transmitter.commands_g import process_group_command
from src.transmitter.contact import add_new_contact, change_nick, contact_setting, remove_contact

View File

@ -22,7 +22,7 @@ along with TFC. If not, see <https://www.gnu.org/licenses/>.
import os
import typing
from typing import Any, Callable, Dict, List, Optional
from typing import Callable, Dict, List, Optional
from src.common.db_logs import remove_logs
from src.common.encoding import b58decode, int_to_bytes
@ -30,7 +30,11 @@ from src.common.exceptions import FunctionReturn
from src.common.input import yes
from src.common.misc import ignored, validate_group_name
from src.common.output import group_management_print, m_print
from src.common.statics import *
from src.common.statics import (ADDED_MEMBERS, ALREADY_MEMBER, GROUP_ADD, GROUP_CREATE, GROUP_DELETE,
GROUP_ID_LENGTH, GROUP_MSG_EXIT_GROUP_HEADER, GROUP_MSG_INVITE_HEADER,
GROUP_MSG_JOIN_HEADER, GROUP_MSG_MEMBER_ADD_HEADER, GROUP_MSG_MEMBER_REM_HEADER,
GROUP_REMOVE, GROUP_RENAME, LOG_REMOVE, NEW_GROUP, NOT_IN_GROUP, RELAY_PACKET_QUEUE,
REMOVED_MEMBERS, UNKNOWN_ACCOUNTS, US_BYTE, WIN_TYPE_CONTACT)
from src.transmitter.packet import queue_command, queue_to_nc
from src.transmitter.user_input import UserInput
@ -42,7 +46,7 @@ if typing.TYPE_CHECKING:
from src.common.db_masterkey import MasterKey
from src.common.db_settings import Settings
from src.transmitter.windows import TxWindow
QueueDict = Dict[bytes, Queue[Any]]
QueueDict = Dict[bytes, Queue[bytes]]
FuncDict = (Dict[str, Callable[[str,
List[bytes],
ContactList,
@ -105,7 +109,7 @@ def process_group_command(user_input: 'UserInput',
func_d = dict(create=group_create,
join =group_create,
add =group_add_member,
rm =group_rm_member) # type: FuncDict
rm =group_rm_member) # type: FuncDict
func = func_d[command_type]
@ -123,7 +127,7 @@ def group_create(group_name: str,
group_id: Optional[bytes] = None
) -> None:
"""Create a new group.
Validate the group name and determine what members can be added.
"""
error_msg = validate_group_name(group_name, contact_list, group_list)

View File

@ -29,7 +29,11 @@ from src.common.exceptions import FunctionReturn
from src.common.input import box_input, yes
from src.common.misc import ignored, validate_key_exchange, validate_nick, validate_onion_addr
from src.common.output import m_print
from src.common.statics import *
from src.common.statics import (ALL, CH_FILE_RECV, CH_LOGGING, CH_NICKNAME, CH_NOTIFY, CONTACT_REM, DISABLE, ECDHE,
ENABLE, KDB_REMOVE_ENTRY_HEADER, KEY_MANAGEMENT_QUEUE, LOGGING, LOG_SETTING_QUEUE,
NOTIFY, ONION_ADDRESS_LENGTH, PSK, RELAY_PACKET_QUEUE, STORE, TRUNC_ADDRESS_LENGTH,
UNENCRYPTED_ADD_NEW_CONTACT, UNENCRYPTED_DATAGRAM_HEADER, UNENCRYPTED_REM_CONTACT,
WIN_TYPE_CONTACT, WIN_TYPE_GROUP)
from src.transmitter.commands_g import group_rename
from src.transmitter.key_exchanges import create_pre_shared_key, start_key_exchange
@ -176,7 +180,7 @@ def remove_contact(user_input: 'UserInput',
# If the last member of the group is removed, deselect
# the group. Deselection is not done in
# update_group_win_members because it would prevent
# `TxWindow.update_window()` because it would prevent
# selecting the empty group for group related commands
# such as notifications.
if not window.window_contacts:

View File

@ -30,7 +30,8 @@ from src.common.crypto import byte_padding, csprng, encrypt_and_sign
from src.common.encoding import int_to_bytes
from src.common.exceptions import FunctionReturn
from src.common.misc import readable_size, split_byte_string
from src.common.statics import *
from src.common.statics import (COMPRESSION_LEVEL, FILE_ETA_FIELD_LENGTH, FILE_PACKET_CTR_LENGTH,
FILE_SIZE_FIELD_LENGTH, PADDING_LENGTH, TRAFFIC_MASKING_QUEUE_CHECK_DELAY, US_BYTE)
if typing.TYPE_CHECKING:
from src.common.db_settings import Settings

View File

@ -24,11 +24,11 @@ import readline
import sys
import typing
from typing import Any, Dict, NoReturn
from typing import Dict, NoReturn
from src.common.exceptions import FunctionReturn
from src.common.misc import get_tab_completer, ignored
from src.common.statics import *
from src.common.statics import COMMAND, FILE, MESSAGE
from src.transmitter.commands import process_command
from src.transmitter.contact import add_new_contact
@ -47,7 +47,7 @@ if typing.TYPE_CHECKING:
from src.common.gateway import Gateway
def input_loop(queues: Dict[bytes, 'Queue[Any]'],
def input_loop(queues: Dict[bytes, 'Queue[bytes]'],
settings: 'Settings',
gateway: 'Gateway',
contact_list: 'ContactList',

View File

@ -32,7 +32,16 @@ from src.common.exceptions import FunctionReturn
from src.common.input import ask_confirmation_code, get_b58_key, nc_bypass_msg, yes
from src.common.output import m_print, phase, print_fingerprint, print_key, print_on_previous_line
from src.common.path import ask_path_gui
from src.common.statics import *
from src.common.statics import (ARGON2_PSK_MEMORY_COST, ARGON2_PSK_PARALLELISM, ARGON2_PSK_TIME_COST,
B58_PUBLIC_KEY, CONFIRM_CODE_LENGTH, DONE, ECDHE, FINGERPRINT, FINGERPRINT_LENGTH,
HEADER_KEY, KDB_ADD_ENTRY_HEADER, KEX_STATUS_HAS_RX_PSK, KEX_STATUS_LOCAL_KEY,
KEX_STATUS_NO_RX_PSK, KEX_STATUS_PENDING, KEX_STATUS_UNVERIFIED,
KEX_STATUS_VERIFIED, KEY_EX_ECDHE, KEY_EX_PSK_RX, KEY_EX_PSK_TX,
KEY_MANAGEMENT_QUEUE, LOCAL_KEY_DATAGRAM_HEADER, LOCAL_KEY_RDY, LOCAL_NICK,
LOCAL_PUBKEY, MESSAGE_KEY, NC_BYPASS_START, NC_BYPASS_STOP,
PUBLIC_KEY_DATAGRAM_HEADER, RELAY_PACKET_QUEUE, RESET, SYMMETRIC_KEY_LENGTH,
TFC_PUBLIC_KEY_LENGTH, UNENCRYPTED_DATAGRAM_HEADER, UNENCRYPTED_ONION_SERVICE_DATA,
WIN_TYPE_GROUP)
from src.transmitter.packet import queue_command, queue_to_nc
@ -484,6 +493,7 @@ def create_pre_shared_key(onion_pub_key: bytes, # Public key of contac
m_print("Error: Did not have permission to write to the directory.", delay=0.5)
continue
c_code = blake2b(onion_pub_key, digest_size=CONFIRM_CODE_LENGTH)
command = (KEY_EX_PSK_TX
+ onion_pub_key
+ tx_mk + csprng()
@ -492,6 +502,21 @@ def create_pre_shared_key(onion_pub_key: bytes, # Public key of contac
queue_command(command, settings, queues)
while True:
purp_code = ask_confirmation_code('Receiver')
if purp_code == c_code.hex():
break
elif purp_code == '':
phase("Resending contact data", head=2)
queue_command(command, settings, queues)
phase(DONE)
print_on_previous_line(reps=5)
else:
m_print("Incorrect confirmation code.", head=1)
print_on_previous_line(reps=4, delay=2)
contact_list.add_contact(onion_pub_key, nick,
bytes(FINGERPRINT_LENGTH), bytes(FINGERPRINT_LENGTH),
KEX_STATUS_NO_RX_PSK,

View File

@ -24,7 +24,7 @@ import os
import typing
import zlib
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional, Tuple, Union
from src.common.crypto import blake2b, byte_padding, csprng, encrypt_and_sign
from src.common.encoding import bool_to_bytes, int_to_bytes, str_to_bytes
@ -33,7 +33,14 @@ from src.common.input import yes
from src.common.misc import split_byte_string
from src.common.output import m_print, phase, print_on_previous_line
from src.common.path import ask_path_gui
from src.common.statics import *
from src.common.statics import (ASSEMBLY_PACKET_LENGTH, COMMAND, COMMAND_DATAGRAM_HEADER, COMMAND_PACKET_QUEUE,
COMPRESSION_LEVEL, C_A_HEADER, C_E_HEADER, C_L_HEADER, C_S_HEADER, DONE, FILE,
FILE_DATAGRAM_HEADER, FILE_KEY_HEADER, FILE_PACKET_CTR_LENGTH, F_A_HEADER,
F_C_HEADER, F_E_HEADER, F_L_HEADER, F_S_HEADER, GROUP_MESSAGE_HEADER,
GROUP_MSG_ID_LENGTH, LOCAL_PUBKEY, MESSAGE, MESSAGE_DATAGRAM_HEADER,
MESSAGE_PACKET_QUEUE, M_A_HEADER, M_C_HEADER, M_E_HEADER, M_L_HEADER, M_S_HEADER,
PADDING_LENGTH, PRIVATE_MESSAGE_HEADER, RELAY_PACKET_QUEUE, TM_COMMAND_PACKET_QUEUE,
TM_FILE_PACKET_QUEUE, TM_MESSAGE_PACKET_QUEUE, WIN_TYPE_GROUP)
from src.transmitter.files import File
from src.transmitter.user_input import UserInput
@ -41,14 +48,16 @@ from src.transmitter.user_input import UserInput
if typing.TYPE_CHECKING:
from multiprocessing import Queue
from src.common.db_keys import KeyList
from src.common.db_masterkey import MasterKey
from src.common.db_settings import Settings
from src.common.gateway import Gateway
from src.transmitter.windows import TxWindow, MockWindow
QueueDict = Dict[bytes, Queue[Any]]
QueueDict = Dict[bytes, Queue[Any]]
log_queue_data = Tuple[Optional[bytes], bytes, Optional[bool], Optional[bool], MasterKey]
def queue_to_nc(packet: bytes,
nc_queue: 'Queue[Any]',
nc_queue: 'Queue[bytes]',
) -> None:
"""Queue unencrypted command/exported file to Networked Computer.
@ -360,7 +369,7 @@ def queue_assembly_packets(assembly_packet_list: List[bytes],
settings: 'Settings',
queues: 'QueueDict',
window: Optional[Union['TxWindow', 'MockWindow']] = None,
log_as_ph: bool = False
log_as_ph: bool = False
) -> None:
"""Queue assembly packets for sender_loop().
@ -388,13 +397,13 @@ def queue_assembly_packets(assembly_packet_list: List[bytes],
queue.put(assembly_packet)
def send_packet(key_list: 'KeyList', # Key list object
gateway: 'Gateway', # Gateway object
log_queue: 'Queue[Any]', # Multiprocessing queue for logged messages
assembly_packet: bytes, # Padded plaintext assembly packet
onion_pub_key: Optional[bytes] = None, # Recipient v3 Onion Service address
log_messages: Optional[bool] = None, # When True, log the message assembly packet
log_as_ph: Optional[bool] = None # When True, log assembly packet as placeholder data
def send_packet(key_list: 'KeyList', # Key list object
gateway: 'Gateway', # Gateway object
log_queue: 'Queue[log_queue_data]', # Multiprocessing queue for logged messages
assembly_packet: bytes, # Padded plaintext assembly packet
onion_pub_key: Optional[bytes] = None, # Recipient v3 Onion Service address
log_messages: Optional[bool] = None, # When True, log the message assembly packet
log_as_ph: Optional[bool] = None # When True, log assembly packet as placeholder data
) -> None:
"""Encrypt and send assembly packet.

View File

@ -25,7 +25,12 @@ import typing
from typing import Any, Dict, List, Optional, Tuple
from src.common.misc import ignored
from src.common.statics import *
from src.common.statics import (COMMAND_PACKET_QUEUE, DATAGRAM_HEADER_LENGTH, EXIT, EXIT_QUEUE, KEY_MANAGEMENT_QUEUE,
LOG_PACKET_QUEUE, MESSAGE_PACKET_QUEUE, RELAY_PACKET_QUEUE, SENDER_MODE_QUEUE,
TM_COMMAND_PACKET_QUEUE, TM_FILE_PACKET_QUEUE, TM_MESSAGE_PACKET_QUEUE,
TM_NOISE_COMMAND_QUEUE, TM_NOISE_PACKET_QUEUE, TRAFFIC_MASKING,
TRAFFIC_MASKING_QUEUE_CHECK_DELAY, UNENCRYPTED_EXIT_COMMAND, UNENCRYPTED_WIPE_COMMAND,
WINDOW_SELECT_QUEUE, WIPE)
from src.transmitter.packet import send_packet
from src.transmitter.traffic_masking import HideRunTime
@ -35,7 +40,6 @@ if typing.TYPE_CHECKING:
from src.common.db_keys import KeyList
from src.common.db_settings import Settings
from src.common.gateway import Gateway
from src.common.db_settings import Settings
QueueDict = Dict[bytes, Queue[Any]]
Message_buffer = Dict[bytes, List[Tuple[bytes, bytes, bool, bool, bytes]]]

View File

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

View File

@ -22,7 +22,7 @@ along with TFC. If not, see <https://www.gnu.org/licenses/>.
import typing
from src.common.output import print_on_previous_line
from src.common.statics import *
from src.common.statics import COMMAND, FILE, MESSAGE, WIN_TYPE_GROUP
if typing.TYPE_CHECKING:
from src.common.db_settings import Settings

View File

@ -27,7 +27,7 @@ from src.common.db_contacts import Contact
from src.common.exceptions import FunctionReturn
from src.common.input import yes
from src.common.output import clear_screen
from src.common.statics import *
from src.common.statics import KEX_STATUS_PENDING, WINDOW_SELECT_QUEUE, WIN_SELECT, WIN_TYPE_CONTACT, WIN_TYPE_GROUP
from src.transmitter.contact import add_new_contact
from src.transmitter.key_exchanges import export_onion_service_data, start_key_exchange

View File

@ -40,11 +40,11 @@ from cryptography.hazmat.primitives.serialization import Encoding, NoEncryptio
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
from src.common.statics import ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM, ARGON2_MIN_TIME_COST, ARGON2_SALT_LENGTH
from src.common.statics import BLAKE2_DIGEST_LENGTH, BLAKE2_DIGEST_LENGTH_MAX, BLAKE2_DIGEST_LENGTH_MIN
from src.common.statics import BLAKE2_KEY_LENGTH_MAX, BLAKE2_PERSON_LENGTH_MAX, BLAKE2_SALT_LENGTH_MAX, PADDING_LENGTH
from src.common.statics import SYMMETRIC_KEY_LENGTH, TFC_PRIVATE_KEY_LENGTH, TFC_PUBLIC_KEY_LENGTH
from src.common.statics import XCHACHA20_NONCE_LENGTH
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,
BLAKE2_DIGEST_LENGTH_MIN, BLAKE2_KEY_LENGTH_MAX, BLAKE2_PERSON_LENGTH_MAX,
BLAKE2_SALT_LENGTH_MAX, PADDING_LENGTH, SYMMETRIC_KEY_LENGTH, TFC_PRIVATE_KEY_LENGTH,
TFC_PUBLIC_KEY_LENGTH, XCHACHA20_NONCE_LENGTH)
from tests.utils import cd_unit_test, cleanup
@ -68,6 +68,7 @@ class TestBLAKE2b(unittest.TestCase):
"""
def setUp(self) -> None:
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
kat_file_url = 'https://raw.githubusercontent.com/BLAKE2/BLAKE2/master/testvectors/blake2b-kat.txt'
@ -109,6 +110,7 @@ class TestBLAKE2b(unittest.TestCase):
self.assertEqual(len(set(digests)), 256)
def tearDown(self) -> None:
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_blake2b_using_the_official_known_answer_tests(self):
@ -144,8 +146,15 @@ class TestBLAKE2bWrapper(unittest.TestCase):
with self.assertRaises(SystemExit):
blake2b(b'test_string', digest_size=invalid_digest_size)
@mock.patch('hashlib.blake2b', return_value=MagicMock(digest=(MagicMock(side_effect=[(BLAKE2_DIGEST_LENGTH-1)*b'a',
(BLAKE2_DIGEST_LENGTH+1)*b'a']))))
@mock.patch('hashlib.blake2b', return_value=MagicMock(digest=(MagicMock(side_effect=[BLAKE2_DIGEST_LENGTH*'a']))))
def test_invalid_blake2b_digest_type_raises_critical_error(self, mock_blake2b):
with self.assertRaises(SystemExit):
blake2b(b'test_string')
mock_blake2b.assert_called()
@mock.patch('hashlib.blake2b', return_value=MagicMock(digest=(
MagicMock(side_effect=[(BLAKE2_DIGEST_LENGTH-1)*b'a',
(BLAKE2_DIGEST_LENGTH+1)*b'a']))))
def test_invalid_size_blake2b_digest_raises_critical_error(self, mock_blake2b):
with self.assertRaises(SystemExit):
blake2b(b'test_string')
@ -175,6 +184,7 @@ class TestArgon2KDF(unittest.TestCase):
"""
def setUp(self) -> None:
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.number_of_tests = 256
@ -197,6 +207,7 @@ class TestArgon2KDF(unittest.TestCase):
subprocess.Popen('make test', shell=True).wait()
def tearDown(self) -> None:
"""Post-test actions."""
os.chdir('..')
cleanup(self.unit_test_dir)
@ -260,6 +271,7 @@ class TestArgon2KDF(unittest.TestCase):
class TestArgon2Wrapper(unittest.TestCase):
def setUp(self) -> None:
"""Pre-test actions."""
self.salt = os.urandom(ARGON2_SALT_LENGTH)
def test_invalid_length_salt_raises_critical_error(self):
@ -267,7 +279,21 @@ class TestArgon2Wrapper(unittest.TestCase):
ARGON2_SALT_LENGTH+1, 1000]]
for invalid_salt in invalid_salts:
with self.assertRaises(SystemExit):
argon2_kdf('password', invalid_salt, ARGON2_MIN_TIME_COST, ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM)
argon2_kdf('password', invalid_salt,
ARGON2_MIN_TIME_COST, ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM)
@mock.patch("argon2.low_level.hash_secret_raw", MagicMock(side_effect=[SYMMETRIC_KEY_LENGTH*'a']))
def test_invalid_type_key_from_argon2_raises_critical_error(self):
with self.assertRaises(SystemExit):
argon2_kdf('password', self.salt, ARGON2_MIN_TIME_COST, ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM)
@mock.patch("argon2.low_level.hash_secret_raw", MagicMock(side_effect=[(SYMMETRIC_KEY_LENGTH-1)*b'a',
(SYMMETRIC_KEY_LENGTH+1)*b'a']))
def test_invalid_size_key_from_argon2_raises_critical_error(self):
with self.assertRaises(SystemExit):
argon2_kdf('password', self.salt, ARGON2_MIN_TIME_COST, ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM)
with self.assertRaises(SystemExit):
argon2_kdf('password', self.salt, ARGON2_MIN_TIME_COST, ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM)
def test_too_small_time_cost_raises_critical_error(self):
with self.assertRaises(SystemExit):
@ -345,6 +371,13 @@ class TestX448(unittest.TestCase):
self.assertIsInstance(public_key, bytes)
self.assertEqual(len(public_key), TFC_PUBLIC_KEY_LENGTH)
def test_deriving_invalid_type_public_key_raises_critical_error(self):
private_key = MagicMock(public_key=MagicMock(return_value=MagicMock(
public_bytes=MagicMock(side_effect=[TFC_PUBLIC_KEY_LENGTH * 'a']))))
with self.assertRaises(SystemExit):
X448.derive_public_key(private_key)
def test_deriving_invalid_size_public_key_raises_critical_error(self):
"""
The public key is already validated by the pyca/cryptography
@ -513,6 +546,7 @@ class TestXChaCha20Poly1305(unittest.TestCase):
nonce_ct_tag_libsodium = libsodium_nonce + libsodium_ct_tag
def setUp(self) -> None:
"""Pre-test actions."""
self.assertEqual(self.ietf_plaintext, self.libsodium_plaintext)
self.assertEqual(self.ietf_ad, self.libsodium_ad)
self.assertEqual(self.ietf_key, self.libsodium_key)
@ -589,6 +623,16 @@ class TestBytePadding(unittest.TestCase):
self.assertEqual(padded_bytestring_lengths, {1*PADDING_LENGTH, 2*PADDING_LENGTH,
3*PADDING_LENGTH, 4*PADDING_LENGTH})
@mock.patch('cryptography.hazmat.primitives.padding.PKCS7',
return_value=MagicMock(
padder=MagicMock(return_value=MagicMock(
update=MagicMock(return_value=''),
finalize=MagicMock(return_value=(PADDING_LENGTH*'a'))))))
def test_invalid_padding_type_raises_critical_error(self, mock_padder):
with self.assertRaises(SystemExit):
byte_padding(b'test_string')
mock_padder.assert_called()
@mock.patch('cryptography.hazmat.primitives.padding.PKCS7',
return_value=MagicMock(
padder=MagicMock(return_value=MagicMock(
@ -663,6 +707,11 @@ class TestCSPRNG(unittest.TestCase):
key = csprng(key_size)
self.assertEqual(len(key), key_size)
@mock.patch('os.getrandom', return_value=SYMMETRIC_KEY_LENGTH*'a')
def test_invalid_entropy_type_from_getrandom_raises_critical_error(self, _):
with self.assertRaises(SystemExit):
csprng()
def test_subceeding_hash_function_min_digest_size_raises_critical_error(self):
with self.assertRaises(SystemExit):
csprng(BLAKE2_DIGEST_LENGTH_MIN-1)

View File

@ -25,7 +25,10 @@ import unittest
from src.common.crypto import encrypt_and_sign
from src.common.db_contacts import Contact, ContactList
from src.common.misc import ensure_dir
from src.common.statics import *
from src.common.statics import (CLEAR_ENTIRE_SCREEN, CONTACT_LENGTH, CURSOR_LEFT_UP_CORNER, DIR_USER_DATA, ECDHE,
FINGERPRINT_LENGTH, KEX_STATUS_HAS_RX_PSK, KEX_STATUS_LOCAL_KEY, KEX_STATUS_NONE,
KEX_STATUS_NO_RX_PSK, KEX_STATUS_PENDING, KEX_STATUS_UNVERIFIED,
KEX_STATUS_VERIFIED, LOCAL_ID, POLY1305_TAG_LENGTH, PSK, XCHACHA20_NONCE_LENGTH)
from tests.mock_classes import create_contact, MasterKey, Settings
from tests.utils import cd_unit_test, cleanup, nick_to_onion_address, nick_to_pub_key, tamper_file, TFCTestCase
@ -34,6 +37,7 @@ from tests.utils import cd_unit_test, cleanup, nick_to_onion_address, nic
class TestContact(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.contact = Contact(nick_to_pub_key('Bob'),
'Bob',
FINGERPRINT_LENGTH * b'\x01',
@ -62,6 +66,7 @@ class TestContact(unittest.TestCase):
class TestContactList(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.master_key = MasterKey()
self.settings = Settings()
@ -73,6 +78,7 @@ class TestContactList(TFCTestCase):
self.real_contact_list.remove(LOCAL_ID)
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_contact_list_iterates_over_contact_objects(self):
@ -101,7 +107,8 @@ class TestContactList(TFCTestCase):
def test_invalid_content_raises_critical_error(self):
# Setup
invalid_data = b'a'
pt_bytes = b''.join([c.serialize_c() for c in self.contact_list.contacts + self.contact_list._dummy_contacts()])
pt_bytes = b''.join([c.serialize_c() for c in self.contact_list.contacts
+ self.contact_list._dummy_contacts()])
ct_bytes = encrypt_and_sign(pt_bytes + invalid_data, self.master_key.master_key)
ensure_dir(DIR_USER_DATA)

View File

@ -27,7 +27,8 @@ from src.common.db_contacts import Contact, ContactList
from src.common.db_groups import Group, GroupList
from src.common.encoding import b58encode
from src.common.misc import ensure_dir
from src.common.statics import *
from src.common.statics import (DIR_USER_DATA, GROUP_DB_HEADER_LENGTH, GROUP_ID_LENGTH, GROUP_STATIC_LENGTH,
ONION_SERVICE_PUBLIC_KEY_LENGTH, POLY1305_TAG_LENGTH, XCHACHA20_NONCE_LENGTH)
from tests.mock_classes import create_contact, group_name_to_group_id, MasterKey, nick_to_pub_key, Settings
from tests.utils import cd_unit_test, cleanup, tamper_file, TFCTestCase
@ -36,6 +37,7 @@ from tests.utils import cd_unit_test, cleanup, tamper_file, TFCTestCase
class TestGroup(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.nicks = ['Alice', 'Bob', 'Charlie']
members = list(map(create_contact, self.nicks))
@ -50,6 +52,7 @@ class TestGroup(unittest.TestCase):
ensure_dir(DIR_USER_DATA)
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_group_iterates_over_contact_objects(self):
@ -117,6 +120,7 @@ class TestGroup(unittest.TestCase):
class TestGroupList(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.master_key = MasterKey()
self.settings = Settings()
@ -146,6 +150,7 @@ class TestGroupList(TFCTestCase):
+ self.settings.max_number_of_group_members * ONION_SERVICE_PUBLIC_KEY_LENGTH)
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_group_list_iterates_over_group_objects(self):

View File

@ -26,7 +26,9 @@ from src.common.crypto import blake2b, encrypt_and_sign
from src.common.db_keys import KeyList, KeySet
from src.common.encoding import int_to_bytes
from src.common.misc import ensure_dir
from src.common.statics import *
from src.common.statics import (DIR_USER_DATA, INITIAL_HARAC, KDB_ADD_ENTRY_HEADER, KDB_CHANGE_MASTER_KEY_HEADER,
KDB_REMOVE_ENTRY_HEADER, KDB_UPDATE_SIZE_HEADER, KEYSET_LENGTH, LOCAL_ID, LOCAL_PUBKEY,
POLY1305_TAG_LENGTH, RX, SYMMETRIC_KEY_LENGTH, TX, XCHACHA20_NONCE_LENGTH)
from tests.mock_classes import create_keyset, MasterKey, nick_to_pub_key, Settings
from tests.utils import cd_unit_test, cleanup, tamper_file
@ -35,6 +37,7 @@ from tests.utils import cd_unit_test, cleanup, tamper_file
class TestKeySet(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.keyset = KeySet(onion_pub_key=nick_to_pub_key('Alice'),
tx_mk=bytes(SYMMETRIC_KEY_LENGTH),
rx_mk=bytes(SYMMETRIC_KEY_LENGTH),
@ -86,6 +89,7 @@ class TestKeySet(unittest.TestCase):
class TestKeyList(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.master_key = MasterKey()
self.settings = Settings()
@ -95,6 +99,7 @@ class TestKeyList(unittest.TestCase):
self.keylist.keysets = [create_keyset(n, store_f=self.keylist.store_keys) for n in self.full_contact_list]
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_storing_and_loading_of_keysets(self):

View File

@ -30,7 +30,12 @@ from unittest import mock
from src.common.db_contacts import ContactList
from src.common.db_logs import access_logs, change_log_db_key, log_writer_loop, remove_logs, write_log_entry
from src.common.encoding import bytes_to_timestamp
from src.common.statics import *
from src.common.statics import (CLEAR_ENTIRE_SCREEN, CURSOR_LEFT_UP_CORNER, C_S_HEADER, DIR_USER_DATA, EXIT,
F_S_HEADER, GROUP_ID_LENGTH, LOGFILE_MASKING_QUEUE, LOG_ENTRY_LENGTH,
LOG_PACKET_QUEUE, LOG_SETTING_QUEUE, MESSAGE, M_A_HEADER, M_C_HEADER, M_S_HEADER,
ORIGIN_CONTACT_HEADER, PADDING_LENGTH, P_N_HEADER, RX, SYMMETRIC_KEY_LENGTH,
TIMESTAMP_LENGTH, TRAFFIC_MASKING_QUEUE, UNIT_TEST_QUEUE, WIN_TYPE_CONTACT,
WIN_TYPE_GROUP)
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
@ -40,12 +45,15 @@ TIMESTAMP_BYTES = bytes.fromhex('08ceae02')
STATIC_TIMESTAMP = bytes_to_timestamp(TIMESTAMP_BYTES).strftime('%H:%M:%S.%f')[:-TIMESTAMP_LENGTH]
SLEEP_DELAY = 0.02
class TestLogWriterLoop(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_function_logs_normal_data(self):
@ -56,10 +64,10 @@ class TestLogWriterLoop(unittest.TestCase):
def queue_delayer():
"""Place messages to queue one at a time."""
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),
(nick_to_pub_key('Alice'), P_N_HEADER + bytes(PADDING_LENGTH), True, True, master_key),
(nick_to_pub_key('Alice'), F_S_HEADER + bytes(PADDING_LENGTH), True, True, 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),
(nick_to_pub_key('Alice'), P_N_HEADER + bytes(PADDING_LENGTH), True, True, master_key),
(nick_to_pub_key('Alice'), F_S_HEADER + bytes(PADDING_LENGTH), True, True, master_key),
(nick_to_pub_key('Alice'), M_S_HEADER + bytes(PADDING_LENGTH), True, False, master_key)]:
queues[LOG_PACKET_QUEUE].put(p)
time.sleep(SLEEP_DELAY)
@ -90,9 +98,9 @@ class TestLogWriterLoop(unittest.TestCase):
def queue_delayer():
"""Place messages to queue one at a time."""
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),
(nick_to_pub_key('Alice'), F_S_HEADER + bytes(PADDING_LENGTH), True, True, 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),
(nick_to_pub_key('Alice'), F_S_HEADER + bytes(PADDING_LENGTH), True, True, master_key),
(nick_to_pub_key('Alice'), M_S_HEADER + bytes(PADDING_LENGTH), True, False, master_key)]:
queues[LOG_PACKET_QUEUE].put(p)
time.sleep(SLEEP_DELAY)
@ -194,12 +202,14 @@ class TestLogWriterLoop(unittest.TestCase):
class TestWriteLogEntry(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.master_key = MasterKey()
self.settings = Settings()
self.log_file = f'{DIR_USER_DATA}{self.settings.software_operation}_logs'
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_oversize_packet_raises_critical_error(self):
@ -220,6 +230,7 @@ class TestWriteLogEntry(unittest.TestCase):
class TestAccessHistoryAndPrintLogs(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.master_key = MasterKey()
self.settings = Settings()
@ -250,6 +261,7 @@ class TestAccessHistoryAndPrintLogs(TFCTestCase):
"s neque a facilisis. Mauris id tortor placerat, aliquam dolor ac, venenatis arcu.")
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_missing_log_file_raises_fr(self):
@ -509,6 +521,7 @@ Log file of message(s) to/from group test_group
class TestReEncrypt(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.old_key = MasterKey()
self.new_key = MasterKey(master_key=os.urandom(SYMMETRIC_KEY_LENGTH))
@ -517,10 +530,11 @@ class TestReEncrypt(TFCTestCase):
self.time = STATIC_TIMESTAMP
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_missing_log_database_raises_fr(self):
self.assert_fr(f"Error: Could not find log database.",
self.assert_fr(f"No log database available.",
change_log_db_key, self.old_key.master_key, self.new_key.master_key, self.settings)
@mock.patch('struct.pack', return_value=TIMESTAMP_BYTES)
@ -569,6 +583,7 @@ Log file of message(s) sent to contact Alice
class TestRemoveLog(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.master_key = MasterKey()
self.settings = Settings()
@ -591,6 +606,7 @@ class TestRemoveLog(TFCTestCase):
"s neque a facilisis. Mauris id tortor placerat, aliquam dolor ac, venenatis arcu.")
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_missing_log_file_raises_fr(self):

View File

@ -28,12 +28,14 @@ from unittest.mock import MagicMock
from src.common.db_masterkey import MasterKey
from src.common.misc import ensure_dir
from src.common.statics import *
from src.common.statics import (DIR_USER_DATA, MASTERKEY_DB_SIZE, PASSWORD_MIN_BIT_STRENGTH,
SYMMETRIC_KEY_LENGTH, TX)
from tests.utils import cd_unit_test, cleanup
KL = SYMMETRIC_KEY_LENGTH
class TestMasterKey(unittest.TestCase):
input_list = ['password', 'different_password', # Invalid new password pair
'password', 'password', # Valid new password pair
@ -41,13 +43,22 @@ class TestMasterKey(unittest.TestCase):
'password'] # Valid login password
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.operation = TX
self.file_name = f"{DIR_USER_DATA}{self.operation}_login_data"
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_password_generation(self):
bit_strength, password = MasterKey.generate_master_password()
self.assertIsInstance(bit_strength, int)
self.assertIsInstance(password, str)
self.assertGreaterEqual(bit_strength, PASSWORD_MIN_BIT_STRENGTH)
self.assertEqual(len(password.split(' ')), 10)
@mock.patch('time.sleep', return_value=None)
def test_invalid_data_in_db_raises_critical_error(self, _):
for delta in [-1, 1]:
@ -60,6 +71,8 @@ class TestMasterKey(unittest.TestCase):
@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('os.popen', return_value=MagicMock(
read=MagicMock(return_value=MagicMock(splitlines=MagicMock(return_value=["MemAvailable 10240"])))))
@mock.patch('os.path.isfile', side_effect=[KeyboardInterrupt, False, True])
@mock.patch('getpass.getpass', side_effect=input_list)
@mock.patch('time.sleep', return_value=None)
@ -75,6 +88,18 @@ class TestMasterKey(unittest.TestCase):
self.assertIsInstance(master_key2.master_key, bytes)
self.assertEqual(master_key.master_key, master_key2.master_key)
@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('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_password_generation(self, *_):
master_key = MasterKey(self.operation, local_test=True)
self.assertIsInstance(master_key.master_key, bytes)
@mock.patch('src.common.db_masterkey.MasterKey.timed_key_derivation',
MagicMock(side_effect= [(KL*b'a', 0.01)]
+ 100 * [(KL*b'b', 5.0)]
@ -83,7 +108,7 @@ class TestMasterKey(unittest.TestCase):
@mock.patch('os.path.isfile', side_effect=[False, True])
@mock.patch('getpass.getpass', side_effect=input_list)
@mock.patch('time.sleep', return_value=None)
def test_kd_binary_serach(self, *_):
def test_kd_binary_search(self, *_):
MasterKey(self.operation, local_test=True)

View File

@ -27,7 +27,8 @@ from unittest import mock
from src.common.crypto import encrypt_and_sign
from src.common.db_onion import OnionService
from src.common.misc import ensure_dir, validate_onion_addr
from src.common.statics import *
from src.common.statics import (DIR_USER_DATA, ONION_SERVICE_PRIVATE_KEY_LENGTH,
POLY1305_TAG_LENGTH, TX, XCHACHA20_NONCE_LENGTH)
from tests.mock_classes import MasterKey
from tests.utils import cd_unit_test, cleanup, tamper_file
@ -36,11 +37,13 @@ from tests.utils import cd_unit_test, cleanup, tamper_file
class TestOnionService(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.master_key = MasterKey()
self.file_name = f"{DIR_USER_DATA}{TX}_onion_db"
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
@mock.patch('time.sleep', return_value=None)
@ -67,7 +70,7 @@ class TestOnionService(unittest.TestCase):
@mock.patch('time.sleep', return_value=None)
def test_loading_invalid_onion_key_raises_critical_error(self, _):
# Setup
ct_bytes = encrypt_and_sign((ONION_SERVICE_PRIVATE_KEY_LENGTH +1) * b'a', self.master_key.master_key)
ct_bytes = encrypt_and_sign((ONION_SERVICE_PRIVATE_KEY_LENGTH + 1) * b'a', self.master_key.master_key)
ensure_dir(DIR_USER_DATA)
with open(f'{DIR_USER_DATA}{TX}_onion_db', 'wb+') as f:

View File

@ -25,7 +25,7 @@ import unittest
from unittest import mock
from src.common.db_settings import Settings
from src.common.statics import *
from src.common.statics import CLEAR_ENTIRE_SCREEN, CURSOR_LEFT_UP_CORNER, DIR_USER_DATA, RX, SETTING_LENGTH, TX
from tests.mock_classes import ContactList, create_group, GroupList, MasterKey
from tests.utils import cd_unit_test, cleanup, tamper_file, TFCTestCase
@ -34,6 +34,7 @@ from tests.utils import cd_unit_test, cleanup, tamper_file, TFCTestCase
class TestSettings(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.file_name = f"{DIR_USER_DATA}{TX}_settings"
self.master_key = MasterKey()
@ -44,6 +45,7 @@ class TestSettings(TFCTestCase):
self.args = self.contact_list, self.group_list
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_invalid_type_raises_critical_error_on_store(self):
@ -99,15 +101,15 @@ class TestSettings(TFCTestCase):
self.assertIsNone(self.settings.change_setting('traffic_masking', 'True', *self.args))
def test_change_settings(self):
self.assert_fr("Error: Invalid value 'Falsee'.",
self.assert_fr("Error: Invalid setting value 'Falsee'.",
self.settings.change_setting, 'disable_gui_dialog', 'Falsee', *self.args)
self.assert_fr("Error: Invalid value '1.1'.",
self.assert_fr("Error: Invalid setting value '1.1'.",
self.settings.change_setting, 'max_number_of_group_members', '1.1', *self.args)
self.assert_fr("Error: Invalid value '18446744073709551616'.",
self.assert_fr("Error: Invalid setting value '18446744073709551616'.",
self.settings.change_setting, 'max_number_of_contacts', str(2 ** 64), *self.args)
self.assert_fr("Error: Invalid value '-1.1'.",
self.assert_fr("Error: Invalid setting value '-1.1'.",
self.settings.change_setting, 'tm_static_delay', '-1.1', *self.args)
self.assert_fr("Error: Invalid value 'True'.",
self.assert_fr("Error: Invalid setting value 'True'.",
self.settings.change_setting, 'tm_static_delay', 'True', *self.args)
self.assertIsNone(self.settings.change_setting('traffic_masking', 'True', *self.args))

View File

@ -29,12 +29,15 @@ from src.common.encoding import b58encode, bool_to_bytes, double_to_bytes, str_t
from src.common.encoding import 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
from src.common.encoding import pub_key_to_onion_address, rm_padding_str, bytes_to_timestamp, b10encode
from src.common.statics import *
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,
PADDING_LENGTH, SYMMETRIC_KEY_LENGTH, TFC_PUBLIC_KEY_LENGTH, TRUNC_ADDRESS_LENGTH)
class TestBase58EncodeAndDecode(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.key = SYMMETRIC_KEY_LENGTH * b'\x01'
def test_encoding_and_decoding_of_random_local_keys(self):
@ -74,7 +77,7 @@ class TestBase58EncodeAndDecode(unittest.TestCase):
byte_key = bytes.fromhex("0C28FCA386C7A227600B2FE50B7CAE11"
"EC86D3BF1FBE471BE89827E19D72AA1D")
b58_key = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"
b58_key = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"
self.assertEqual(b58encode(byte_key), b58_key)
self.assertEqual(b58decode(b58_key), byte_key)

View File

@ -33,7 +33,7 @@ from src.common.crypto import blake2b
from src.common.gateway import gateway_loop, Gateway, GatewaySettings
from src.common.misc import ensure_dir
from src.common.reed_solomon import RSCodec
from src.common.statics import *
from src.common.statics import DIR_USER_DATA, GATEWAY_QUEUE, NC, PACKET_CHECKSUM_LENGTH, RX, TX
from tests.mock_classes import Settings
from tests.utils import cd_unit_test, cleanup, gen_queue_dict, tear_queues, TFCTestCase
@ -42,10 +42,12 @@ from tests.utils import cd_unit_test, cleanup, gen_queue_dict, tear_queue
class TestGatewayLoop(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.queues = gen_queue_dict()
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
tear_queues(self.queues)
@ -63,10 +65,12 @@ class TestGatewayLoop(unittest.TestCase):
class TestGatewaySerial(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.settings = Settings(session_usb_serial_adapter=True)
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
@mock.patch('time.sleep', return_value=None)
@ -239,6 +243,7 @@ class TestGatewaySerial(TFCTestCase):
class TestGatewaySettings(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.default_serialized = """\
{
@ -249,6 +254,7 @@ class TestGatewaySettings(TFCTestCase):
}"""
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyS0'], ['ttyUSB0'], ['ttyS0']])
@ -462,13 +468,13 @@ class TestGatewaySettings(TFCTestCase):
@mock.patch('time.sleep', return_value=None)
def test_change_setting(self, _):
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True)
self.assert_fr("Error: Invalid value 'Falsee'.",
self.assert_fr("Error: Invalid setting value 'Falsee'.",
settings.change_setting, 'serial_baudrate', 'Falsee')
self.assert_fr("Error: Invalid value '1.1'.",
self.assert_fr("Error: Invalid setting value '1.1'.",
settings.change_setting, 'serial_baudrate', '1.1', )
self.assert_fr("Error: Invalid value '18446744073709551616'.",
self.assert_fr("Error: Invalid setting value '18446744073709551616'.",
settings.change_setting, 'serial_baudrate', str(2 ** 64))
self.assert_fr("Error: Invalid value 'Falsee'.",
self.assert_fr("Error: Invalid setting value 'Falsee'.",
settings.change_setting, 'use_serial_usb_adapter', 'Falsee')
self.assertIsNone(settings.change_setting('serial_baudrate', '9600'))

View File

@ -24,7 +24,8 @@ import unittest
from unittest import mock
from src.common.input import ask_confirmation_code, box_input, get_b58_key, nc_bypass_msg, pwd_prompt, yes
from src.common.statics import *
from src.common.statics import (B58_LOCAL_KEY, B58_PUBLIC_KEY, NC_BYPASS_START, NC_BYPASS_STOP, SYMMETRIC_KEY_LENGTH,
TFC_PUBLIC_KEY_LENGTH)
from tests.mock_classes import Settings
from tests.utils import nick_to_short_address, VALID_ECDHE_PUB_KEY, VALID_LOCAL_KEY_KDK
@ -53,6 +54,7 @@ class TestBoxInput(unittest.TestCase):
class TestGetB58Key(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
@mock.patch('time.sleep', return_value=None)

View File

@ -35,7 +35,8 @@ from src.common.misc import get_tab_completer, get_terminal_height, get_termi
from src.common.misc import process_arguments, readable_size, round_up, separate_header, separate_headers
from src.common.misc import separate_trailer, split_string, split_byte_string, terminal_width_check
from src.common.misc import validate_group_name, validate_key_exchange, validate_onion_addr, validate_nick
from src.common.statics import *
from src.common.statics import (DIR_RECV_FILES, DIR_USER_DATA, DUMMY_GROUP, ECDHE, EXIT, EXIT_QUEUE, LOCAL_ID,
PADDING_LENGTH, RX, TAILS, WIPE)
from tests.mock_classes import ContactList, Gateway, GroupList, Settings
from tests.utils import cd_unit_test, cleanup, gen_queue_dict, ignored, nick_to_onion_address
@ -45,6 +46,7 @@ from tests.utils import nick_to_pub_key, tear_queues, TFCTestCase
class TestCalculateRaceConditionDelay(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
def test_race_condition_delay_calculation(self):
@ -54,6 +56,7 @@ class TestCalculateRaceConditionDelay(unittest.TestCase):
class TestDecompress(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
self.settings.max_decompress_size = 1000
@ -78,6 +81,7 @@ class TestDecompress(TFCTestCase):
class TestEnsureDir(unittest.TestCase):
def tearDown(self):
"""Post-test actions."""
with ignored(OSError):
os.rmdir('test_dir/')
@ -90,6 +94,7 @@ class TestEnsureDir(unittest.TestCase):
class TestTabCompleteList(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice', 'Bob'])
self.group_list = GroupList(groups=['test_group'])
self.settings = Settings(key_list=['key1', 'key2'])
@ -145,10 +150,12 @@ class TestIgnored(unittest.TestCase):
class TestMonitorProcesses(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.settings = Settings()
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
@staticmethod
@ -218,13 +225,13 @@ class TestMonitorProcesses(TFCTestCase):
monitor_processes(process_list, RX, queues)
self.assertFalse(os.path.isdir(DIR_USER_DATA))
self.assertFalse(os.path.isdir(DIR_RECV_FILES))
mock_os_system.assert_called_with('poweroff')
mock_os_system.assert_called_with('systemctl poweroff')
tear_queues(queues)
@mock.patch('time.sleep', return_value=None)
@mock.patch('os.system', return_value=None)
@mock.patch('subprocess.check_output', lambda *popenargs, timeout=None, **kwargs: TAILS)
@mock.patch('builtins.open', mock.mock_open(read_data=TAILS))
def test_wipe_tails(self, mock_os_system, *_):
queues = gen_queue_dict()
process_list = [Process(target=self.mock_process)]
@ -244,7 +251,7 @@ class TestMonitorProcesses(TFCTestCase):
with self.assertRaises(SystemExit):
monitor_processes(process_list, RX, queues)
mock_os_system.assert_called_with('poweroff')
mock_os_system.assert_called_with('systemctl poweroff')
# Test that user data wasn't removed
self.assertTrue(os.path.isdir(DIR_USER_DATA))
@ -254,6 +261,8 @@ class TestMonitorProcesses(TFCTestCase):
class TestProcessArguments(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
class MockParser(object):
"""MockParse object."""
def __init__(self, *_, **__):
@ -280,6 +289,7 @@ class TestProcessArguments(unittest.TestCase):
argparse.ArgumentParser = MockParser
def tearDown(self):
"""Post-test actions."""
argparse.ArgumentParser = self.o_argparse
def test_process_arguments(self):
@ -429,6 +439,7 @@ class TestValidateOnionAddr(unittest.TestCase):
class TestValidateGroupName(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice'])
self.group_list = GroupList(groups=['test_group'])
@ -467,6 +478,7 @@ class TestValidateKeyExchange(unittest.TestCase):
class TestValidateNick(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice', 'Bob'])
self.group_list = GroupList(groups=['test_group'])

View File

@ -26,7 +26,10 @@ from unittest import mock
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
from src.common.statics import *
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,
NORMAL_TEXT, NOT_IN_GROUP, REMOVED_MEMBERS, RX, SYMMETRIC_KEY_LENGTH, TX,
UNKNOWN_ACCOUNTS, VERSION)
from tests.mock_classes import ContactList, nick_to_pub_key, Settings
from tests.utils import TFCTestCase
@ -41,6 +44,7 @@ class TestClearScreen(TFCTestCase):
class TestGroupManagementPrint(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice'])
self.lines = [nick_to_pub_key('Alice'), nick_to_pub_key('Bob')]
self.group_name = 'test_group'
@ -238,6 +242,7 @@ class TestPrintFingerprint(TFCTestCase):
class TestPrintKey(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
def test_print_kdk(self):
@ -294,6 +299,7 @@ class TestPrintSpacing(TFCTestCase):
class TestRPPrint(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.now()
self.timestamp = self.ts.strftime("%b %d - %H:%M:%S.%f")[:-4]

View File

@ -38,6 +38,7 @@ class TestAskPathGui(TFCTestCase):
path = '/home/user/'
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
@mock.patch('os.path.isfile', return_value=True)
@ -78,6 +79,7 @@ class TestAskPathGui(TFCTestCase):
class TestCompleter(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.cwd = os.getcwd()
self.unit_test_dir = cd_unit_test()
@ -93,6 +95,7 @@ class TestCompleter(unittest.TestCase):
os.chdir('..')
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
os.chdir(self.cwd)
@ -116,10 +119,12 @@ class TestCompleter(unittest.TestCase):
class TestPath(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
with ignored(FileExistsError):
os.mkdir('test_dir/')
def tearDown(self):
"""Post-test actions."""
with ignored(OSError):
os.remove('testfile')
with ignored(OSError):

View File

@ -16,7 +16,10 @@ import unittest
from random import sample
from src.common.reed_solomon import *
from src.common.reed_solomon import (RSCodec, ReedSolomonError, find_prime_polys, gf_add, gf_div, gf_mul, gf_mult_nolut,
gf_mult_nolut_slow, gf_neg, gf_poly_mul, gf_poly_mul_simple, gf_poly_neg, gf_sub,
init_tables, itertools, rs_check, rs_correct_msg, rs_correct_msg_nofsynd,
rs_encode_msg, rs_generator_poly, rs_generator_poly_all, rs_simple_encode_msg)
class TestReedSolomon(unittest.TestCase):
@ -75,8 +78,8 @@ class TestReedSolomon(unittest.TestCase):
kk = 18
tt = nn - kk
rs = RSCodec(tt, fcr=120, prim=0x187)
hexencmsg = '00faa123555555c000000354064432' \
'c02800fe97c434e1ff5365cf8fafe4'
hexencmsg = ('00faa123555555c000000354064432'
'c02800fe97c434e1ff5365cf8fafe4')
strf = str
encmsg = bytearray.fromhex(strf(hexencmsg))
decmsg = encmsg[:kk]
@ -108,8 +111,8 @@ class TestReedSolomon(unittest.TestCase):
kk = 34
tt = nn - kk
rs = RSCodec(tt, fcr=120, prim=0x187)
hexencmsg = '08faa123555555c000000354064432c0280e1b4d090cfc04' \
'887400000003500000000e1985ff9c6b33066ca9f43d12e8'
hexencmsg = ('08faa123555555c000000354064432c0280e1b4d090cfc04'
'887400000003500000000e1985ff9c6b33066ca9f43d12e8')
strf = str
encmsg = bytearray.fromhex(strf(hexencmsg))
decmsg = encmsg[:kk]

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3.7
# -*- coding: utf-8 -*-
"""
TFC - Onion-routed, endpoint secure messaging system
Copyright (C) 2013-2019 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/>.
"""
import unittest
from src.common.word_list import eff_wordlist
class TestWordList(unittest.TestCase):
def test_each_word_is_unique(self):
self.assertEqual(len(eff_wordlist),
len(set(eff_wordlist)))
def test_word_list_length(self):
self.assertEqual(len(eff_wordlist),
7776)
if __name__ == '__main__':
unittest.main()

View File

@ -41,7 +41,9 @@ from src.common.db_settings import Settings as OrigSettings
from src.common.encoding import pub_key_to_onion_address, pub_key_to_short_address
from src.common.misc import calculate_race_condition_delay
from src.common.reed_solomon import RSCodec
from src.common.statics import *
from src.common.statics import (DIR_USER_DATA, FINGERPRINT_LENGTH, INITIAL_HARAC, KEX_STATUS_VERIFIED, LOCAL_ID,
LOCAL_NICK, LOCAL_PUBKEY, ONION_SERVICE_PRIVATE_KEY_LENGTH, SYMMETRIC_KEY_LENGTH,
TX, WIN_TYPE_GROUP, WIN_UID_LOCAL)
from src.transmitter.windows import TxWindow as OrigTxWindow
@ -209,11 +211,13 @@ class MasterKey(OrigMasterKey):
setattr(self, key, value)
def load_master_key(self) -> bytes:
"""Create mock master key bytes."""
if getpass.getpass() == 'test_password':
return self.master_key
else:
return SYMMETRIC_KEY_LENGTH * b'f'
class OnionService(OrigOnionService):
"""Mock the object for unit testing."""

View File

@ -30,7 +30,10 @@ from unittest.mock import MagicMock
from src.common.db_logs import write_log_entry
from src.common.encoding import int_to_bytes
from src.common.statics import *
from src.common.statics import (CH_FILE_RECV, CH_LOGGING, CH_NOTIFY, CLEAR_ENTIRE_LINE, COMMAND, CURSOR_UP_ONE_LINE,
C_L_HEADER, DISABLE, ENABLE, F_S_HEADER, LOCAL_ID, LOCAL_PUBKEY, LOG_REMOVE, MESSAGE,
ORIGIN_CONTACT_HEADER, PADDING_LENGTH, RESET, RX, SYMMETRIC_KEY_LENGTH, US_BYTE,
WIN_TYPE_CONTACT, WIN_TYPE_GROUP, WIN_UID_FILE, WIPE)
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
@ -38,13 +41,14 @@ from src.receiver.commands import process_command, remove_log, reset_screen, win
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
from tests.utils import assembly_packet_creator, cd_unit_test, cleanup, ignored, nick_to_short_address, tear_queue
from tests.utils import TFCTestCase
from tests.utils import assembly_packet_creator, cd_unit_test, cleanup, ignored, nick_to_short_address
from tests.utils import tear_queue, TFCTestCase
class TestProcessCommand(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.ts = datetime.now()
self.settings = Settings()
@ -62,6 +66,7 @@ class TestProcessCommand(TFCTestCase):
self.settings, self.master_key, self.gateway, self.exit_queue)
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
tear_queue(self.exit_queue)
@ -81,6 +86,7 @@ class TestProcessCommand(TFCTestCase):
class TestWinActivity(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.window_list = WindowList()
self.window_list.windows = [RxWindow(name='Alice', unread_messages=4),
RxWindow(name='Bob', unread_messages=15)]
@ -99,6 +105,7 @@ class TestWinActivity(TFCTestCase):
class TestWinSelect(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.window_list = WindowList()
self.window_list.windows = [RxWindow(uid=nick_to_pub_key("Alice"), name='Alice'),
RxWindow(uid=nick_to_pub_key("Bob"), name='Bob')]
@ -117,6 +124,7 @@ class TestWinSelect(unittest.TestCase):
class TestResetScreen(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.cmd_data = nick_to_pub_key("Alice")
self.window_list = WindowList()
self.window_list.windows = [RxWindow(uid=nick_to_pub_key("Alice"), name='Alice'),
@ -141,9 +149,11 @@ class TestResetScreen(unittest.TestCase):
class TestExitTFC(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.exit_queue = Queue()
def tearDown(self):
"""Post-test actions."""
tear_queue(self.exit_queue)
def test_function(self):
@ -154,6 +164,7 @@ class TestExitTFC(unittest.TestCase):
class TestLogCommand(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.cmd_data = int_to_bytes(1) + nick_to_pub_key("Bob")
self.ts = datetime.now()
@ -173,6 +184,7 @@ class TestLogCommand(TFCTestCase):
self.time = datetime.fromtimestamp(time_float).strftime("%H:%M:%S.%f")[:-4]
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
with ignored(OSError):
os.remove('Receiver - Plaintext log (None)')
@ -204,6 +216,7 @@ Log file of 1 most recent message(s) to/from contact Bob
class TestRemoveLog(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.win_name = nick_to_pub_key("Alice")
self.contact_list = ContactList()
@ -212,6 +225,7 @@ class TestRemoveLog(TFCTestCase):
self.master_key = MasterKey()
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_remove_log_file(self):
@ -222,6 +236,7 @@ class TestRemoveLog(TFCTestCase):
class TestChMasterKey(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.ts = datetime.now()
self.master_key = MasterKey()
@ -234,11 +249,13 @@ class TestChMasterKey(TFCTestCase):
self.key_list, self.settings, self.master_key)
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
@mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 0.1)
@mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 1.0)
@mock.patch('os.popen', return_value=MagicMock(read=MagicMock(return_value=MagicMock(splitlines=MagicMock(return_value=["MemFree 10240"])))))
@mock.patch('os.popen', return_value=MagicMock(
read=MagicMock(return_value=MagicMock(splitlines=MagicMock(return_value=["MemAvailable 10240"])))))
@mock.patch('multiprocessing.cpu_count', return_value=1)
@mock.patch('getpass.getpass', return_value='a')
@mock.patch('time.sleep', return_value=None)
@ -261,6 +278,7 @@ class TestChMasterKey(TFCTestCase):
class TestChNick(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.now()
self.contact_list = ContactList(nicks=['Alice'])
self.window_list = WindowList(contact_list=self.contact_list)
@ -290,6 +308,7 @@ class TestChNick(TFCTestCase):
class TestChSetting(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.now()
self.window_list = WindowList()
self.contact_list = ContactList()
@ -339,6 +358,7 @@ class TestChSetting(TFCTestCase):
class TestChContactSetting(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.fromtimestamp(1502750000)
self.contact_list = ContactList(nicks=['Alice', 'Bob'])
self.group_list = GroupList(groups=['test_group', 'test_group2'])
@ -425,6 +445,7 @@ class TestChContactSetting(TFCTestCase):
class TestContactRemove(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.ts = datetime.now()
self.window_list = WindowList()
@ -434,6 +455,7 @@ class TestContactRemove(TFCTestCase):
self.args = self.cmd_data, self.ts, self.window_list
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_no_contact_raises_fr(self):
@ -466,9 +488,11 @@ class TestContactRemove(TFCTestCase):
class TestWipe(unittest.TestCase):
def setUp(self) -> None:
"""Pre-test actions."""
self.exit_queue = Queue()
def tearDown(self) -> None:
"""Post-test actions."""
tear_queue(self.exit_queue)
@mock.patch('os.system', return_value=None)

View File

@ -22,7 +22,8 @@ along with TFC. If not, see <https://www.gnu.org/licenses/>.
import datetime
import unittest
from src.common.statics import *
from src.common.statics import US_BYTE
from src.receiver.commands_g import group_add, group_create, group_delete, group_remove, group_rename
from tests.mock_classes import Contact, ContactList, GroupList, RxWindow, Settings, WindowList
@ -32,6 +33,7 @@ from tests.utils import group_name_to_group_id, nick_to_pub_key, TFCTestC
class TestGroupCreate(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.datetime.now()
self.settings = Settings()
self.window_list = WindowList()
@ -78,6 +80,7 @@ class TestGroupCreate(TFCTestCase):
class TestGroupAdd(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.datetime.now()
self.settings = Settings()
self.window_list = WindowList()
@ -125,6 +128,7 @@ class TestGroupAdd(TFCTestCase):
class TestGroupRemove(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.datetime.now()
self.window_list = WindowList()
self.contact_list = ContactList(nicks=[f"contact_{n}" for n in range(21)])
@ -152,6 +156,7 @@ class TestGroupRemove(TFCTestCase):
class TestGroupDelete(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.datetime.now()
self.window_list = WindowList()
self.group_list = GroupList(groups=['test_group'])
@ -179,6 +184,7 @@ class TestGroupDelete(TFCTestCase):
class TestGroupRename(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.datetime.now()
self.group_list = GroupList(groups=['test_group'])
self.window_list = WindowList()
@ -186,7 +192,7 @@ class TestGroupRename(TFCTestCase):
self.window_list.windows = [self.window]
self.contact_list = ContactList(nicks=['alice'])
self.args = self.ts, self.window_list, self.contact_list, self.group_list
def test_missing_group_id_raises_fr(self):
# Setup
cmd_data = group_name_to_group_id('test_group2') + b'new_name'

View File

@ -28,7 +28,7 @@ from unittest import mock
from src.common.crypto import blake2b, encrypt_and_sign
from src.common.encoding import str_to_bytes
from src.common.statics import *
from src.common.statics import COMPRESSION_LEVEL, DIR_RECV_FILES, ORIGIN_CONTACT_HEADER, SYMMETRIC_KEY_LENGTH, US_BYTE
from src.receiver.files import new_file, process_assembled_file, process_file, store_unique
@ -39,12 +39,14 @@ from tests.utils import cd_unit_test, cleanup, nick_to_pub_key, TFCTestCa
class TestStoreUnique(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.file_data = os.urandom(100)
self.file_dir = 'test_dir/'
self.file_name = 'test_file'
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_each_file_is_store_with_unique_name(self):
@ -56,16 +58,18 @@ class TestStoreUnique(unittest.TestCase):
class ProcessAssembledFile(TFCTestCase):
def setUp(self):
self.unit_test_dir = cd_unit_test()
self.ts = datetime.now()
self.onion_pub_key = nick_to_pub_key('Alice')
self.nick = 'Alice'
self.settings = Settings()
self.window_list = WindowList(nick=['Alice', 'Bob'])
self.key = os.urandom(SYMMETRIC_KEY_LENGTH)
self.args = self.onion_pub_key, self.nick, self.settings, self.window_list
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.ts = datetime.now()
self.onion_pub_key = nick_to_pub_key('Alice')
self.nick = 'Alice'
self.settings = Settings()
self.window_list = WindowList(nick=['Alice', 'Bob'])
self.key = os.urandom(SYMMETRIC_KEY_LENGTH)
self.args = self.onion_pub_key, self.nick, self.settings, self.window_list
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_invalid_structure_raises_fr(self):
@ -156,6 +160,7 @@ class ProcessAssembledFile(TFCTestCase):
class TestNewFile(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.ts = datetime.now()
self.packet = b''
@ -169,6 +174,7 @@ class TestNewFile(TFCTestCase):
self.args = self.file_keys, self.file_buf, self.contact_list, self.window_list, self.settings
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_unknown_account_raises_fr(self):
@ -215,6 +221,7 @@ class TestNewFile(TFCTestCase):
class TestProcessFile(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.ts = datetime.now()
self.account = nick_to_pub_key('Alice')
@ -226,6 +233,7 @@ class TestProcessFile(TFCTestCase):
self.args = self.file_key, self.contact_list, self.window_list, self.settings
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_invalid_key_raises_fr(self):

View File

@ -32,7 +32,9 @@ from unittest.mock import MagicMock
from src.common.crypto import argon2_kdf, encrypt_and_sign
from src.common.encoding import b58encode, str_to_bytes
from src.common.exceptions import FunctionReturn
from src.common.statics import *
from src.common.statics import (ARGON2_SALT_LENGTH, BOLD_ON, CLEAR_ENTIRE_SCREEN, CONFIRM_CODE_LENGTH,
CURSOR_LEFT_UP_CORNER, FINGERPRINT_LENGTH, LOCAL_ID, NORMAL_TEXT, PSK_FILE_SIZE,
SYMMETRIC_KEY_LENGTH, WIN_TYPE_CONTACT, WIN_UID_LOCAL, XCHACHA20_NONCE_LENGTH)
from src.receiver.key_exchanges import key_ex_ecdhe, key_ex_psk_rx, key_ex_psk_tx, local_key_rdy, process_local_key
@ -47,6 +49,7 @@ class TestProcessLocalKey(TFCTestCase):
new_kek = os.urandom(SYMMETRIC_KEY_LENGTH)
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=[LOCAL_ID, 'Alice'])
self.key_list = KeyList( nicks=[LOCAL_ID, 'Alice'])
self.window_list = WindowList( nicks=[LOCAL_ID, 'Alice'])
@ -59,10 +62,11 @@ class TestProcessLocalKey(TFCTestCase):
self.hek = os.urandom(SYMMETRIC_KEY_LENGTH)
self.conf_code = os.urandom(CONFIRM_CODE_LENGTH)
self.packet = encrypt_and_sign(self.key + self.hek + self.conf_code, key=self.kek)
self.args = (self.window_list, self.contact_list, self.key_list, self.settings,
self.args = (self.window_list, self.contact_list, self.key_list, self.settings,
self.kdk_hashes, self.packet_hashes, self.l_queue)
def tearDown(self):
"""Post-test actions."""
tear_queue(self.l_queue)
@mock.patch('tkinter.Tk', return_value=MagicMock())
@ -150,6 +154,7 @@ class TestProcessLocalKey(TFCTestCase):
class TestLocalKeyRdy(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.fromtimestamp(1502750000)
@mock.patch('time.sleep', return_value=None)
@ -181,6 +186,7 @@ class TestLocalKeyRdy(TFCTestCase):
class TestKeyExECDHE(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.fromtimestamp(1502750000)
self.window_list = WindowList(nicks=[LOCAL_ID])
self.contact_list = ContactList()
@ -230,6 +236,7 @@ class TestKeyExECDHE(TFCTestCase):
class TestKeyExPSKTx(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.ts = datetime.fromtimestamp(1502750000)
self.window_list = WindowList(nicks=[LOCAL_ID])
self.contact_list = ContactList()
@ -282,6 +289,7 @@ class TestKeyExPSKRx(TFCTestCase):
file_name = f"{nick_to_short_address('User')}.psk - give to {nick_to_short_address('Alice')}"
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.packet = b'\x00' + nick_to_pub_key("Alice")
self.ts = datetime.now()
@ -293,6 +301,7 @@ class TestKeyExPSKRx(TFCTestCase):
self.args = self.packet, self.ts, self.window_list, self.contact_list, self.key_list, self.settings
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_unknown_account_raises_fr(self):
@ -359,14 +368,14 @@ class TestKeyExPSKRx(TFCTestCase):
@mock.patch('getpass.getpass', return_value='test_password')
def test_valid_psk(self, *_):
# Setup
keyset = self.key_list.get_keyset(nick_to_pub_key("Alice"))
keyset.rx_mk = bytes(SYMMETRIC_KEY_LENGTH)
keyset.rx_hk = bytes(SYMMETRIC_KEY_LENGTH)
salt = os.urandom(ARGON2_SALT_LENGTH)
rx_key = os.urandom(SYMMETRIC_KEY_LENGTH)
rx_hek = os.urandom(SYMMETRIC_KEY_LENGTH)
kek = argon2_kdf('test_password', salt, time_cost=1, memory_cost=100, parallelism=1)
ct_tag = encrypt_and_sign(rx_key + rx_hek, key=kek)
keyset = self.key_list.get_keyset(nick_to_pub_key("Alice"))
keyset.rx_mk = bytes(SYMMETRIC_KEY_LENGTH)
keyset.rx_hk = bytes(SYMMETRIC_KEY_LENGTH)
salt = os.urandom(ARGON2_SALT_LENGTH)
rx_key = os.urandom(SYMMETRIC_KEY_LENGTH)
rx_hek = os.urandom(SYMMETRIC_KEY_LENGTH)
kek = argon2_kdf('test_password', salt, time_cost=1, memory_cost=100, parallelism=1)
ct_tag = encrypt_and_sign(rx_key + rx_hek, key=kek)
with open(self.file_name, 'wb+') as f:
f.write(salt + ct_tag)
@ -387,9 +396,9 @@ class TestKeyExPSKRx(TFCTestCase):
@mock.patch('getpass.getpass', return_value='test_password')
def test_valid_psk_overwrite_failure(self, *_):
# Setup
keyset = self.key_list.get_keyset(nick_to_pub_key("Alice"))
keyset.rx_mk = bytes(SYMMETRIC_KEY_LENGTH)
keyset.rx_hk = bytes(SYMMETRIC_KEY_LENGTH)
keyset = self.key_list.get_keyset(nick_to_pub_key("Alice"))
keyset.rx_mk = bytes(SYMMETRIC_KEY_LENGTH)
keyset.rx_hk = bytes(SYMMETRIC_KEY_LENGTH)
salt = os.urandom(ARGON2_SALT_LENGTH)
rx_key = os.urandom(SYMMETRIC_KEY_LENGTH)

View File

@ -28,7 +28,9 @@ from unittest import mock
from src.common.encoding import bool_to_bytes
from src.common.misc import ensure_dir
from src.common.statics import *
from src.common.statics import (BLAKE2_DIGEST_LENGTH, DIR_USER_DATA, FILE, FILE_KEY_HEADER, GROUP_ID_LENGTH, LOCAL_ID,
LOCAL_PUBKEY, LOG_ENTRY_LENGTH, MESSAGE, MESSAGE_LENGTH, ORIGIN_CONTACT_HEADER,
ORIGIN_USER_HEADER, SYMMETRIC_KEY_LENGTH)
from src.receiver.messages import process_message
from src.receiver.packet import PacketList
@ -42,6 +44,7 @@ from tests.utils import nick_to_pub_key, TFCTestCase
class TestProcessMessage(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.msg = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean condimentum consectetur purus quis"
@ -64,18 +67,19 @@ class TestProcessMessage(TFCTestCase):
self.key_list = KeyList( nicks=['Alice', 'Bob', 'Charlie', LOCAL_ID])
self.group_list = GroupList( groups=['test_group'])
self.packet_list = PacketList(contact_list=self.contact_list, settings=self.settings)
self.window_list = WindowList(contact_list=self.contact_list, settings=self.settings,
self.window_list = WindowList(contact_list=self.contact_list, settings=self.settings,
group_list=self.group_list, packet_list=self.packet_list)
self.group_id = group_name_to_group_id('test_group')
self.file_keys = dict()
self.group_list.get_group('test_group').log_messages = True
self.args = (self.window_list, self.packet_list, self.contact_list, self.key_list,
self.args = (self.window_list, self.packet_list, self.contact_list, self.key_list,
self.group_list, self.settings, self.master_key, self.file_keys)
ensure_dir(DIR_USER_DATA)
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
# Invalid packets

View File

@ -31,7 +31,11 @@ from unittest.mock import MagicMock
from src.common.crypto import blake2b, encrypt_and_sign
from src.common.encoding import b58encode, bool_to_bytes, int_to_bytes, str_to_bytes
from src.common.statics import *
from src.common.statics import (CH_FILE_RECV, COMMAND, COMMAND_DATAGRAM_HEADER, CONFIRM_CODE_LENGTH, ENABLE, EXIT,
FILE_DATAGRAM_HEADER, FILE_KEY_HEADER, INITIAL_HARAC, KEY_EX_ECDHE,
LOCAL_KEY_DATAGRAM_HEADER, MESSAGE, MESSAGE_DATAGRAM_HEADER, ORIGIN_CONTACT_HEADER,
PRIVATE_MESSAGE_HEADER, SYMMETRIC_KEY_LENGTH, UNIT_TEST_QUEUE, US_BYTE, WIN_SELECT,
WIN_UID_FILE, WIN_UID_LOCAL)
from src.transmitter.packet import split_to_assembly_packets
@ -49,10 +53,12 @@ def rotate_key(key: bytes, harac: int) -> Tuple[bytes, int]:
class TestOutputLoop(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.o_sleep = time.sleep
time.sleep = lambda _: None
def tearDown(self):
"""Post-test actions."""
time.sleep = self.o_sleep
@mock.patch('tkinter.Tk', return_value=MagicMock())

View File

@ -28,7 +28,9 @@ from unittest import mock
from src.common.crypto import byte_padding, encrypt_and_sign
from src.common.encoding import int_to_bytes
from src.common.statics import *
from src.common.statics import (COMMAND, COMPRESSION_LEVEL, DIR_RECV_FILES, FILE, F_C_HEADER, LOCAL_ID, MESSAGE,
M_A_HEADER, M_E_HEADER, ORIGIN_CONTACT_HEADER, ORIGIN_USER_HEADER, PADDING_LENGTH,
PRIVATE_MESSAGE_HEADER, P_N_HEADER, SYMMETRIC_KEY_LENGTH, US_BYTE)
from src.transmitter.packet import split_to_assembly_packets
@ -42,6 +44,7 @@ from tests.utils import UNDECODABLE_UNICODE
class TestDecryptAssemblyPacket(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.onion_pub_key = nick_to_pub_key("Alice")
self.origin = ORIGIN_CONTACT_HEADER
self.window_list = WindowList(nicks=['Alice', LOCAL_ID])
@ -113,6 +116,7 @@ class TestDecryptAssemblyPacket(TFCTestCase):
class TestPacket(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.short_msg = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
self.msg = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean condimentum consectetur purus quis"
" dapibus. Fusce venenatis lacus ut rhoncus faucibus. Cras sollicitudin commodo sapien, sed bibendu"
@ -140,6 +144,7 @@ class TestPacket(TFCTestCase):
self.short_f_data = (int_to_bytes(1) + int_to_bytes(2) + b'testfile.txt' + US_BYTE + encrypted)
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_invalid_assembly_packet_header_raises_fr(self):
@ -412,6 +417,7 @@ class TestPacket(TFCTestCase):
class TestPacketList(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice', 'Bob'])
self.settings = Settings()
self.onion_pub_key = nick_to_pub_key('Alice')

View File

@ -28,7 +28,9 @@ from multiprocessing import Queue
from src.common.encoding import int_to_bytes
from src.common.reed_solomon import RSCodec
from src.common.statics import *
from src.common.statics import (COMMAND_DATAGRAM_HEADER, FILE_DATAGRAM_HEADER, GATEWAY_QUEUE,
LOCAL_KEY_DATAGRAM_HEADER, MESSAGE_DATAGRAM_HEADER,
ONION_SERVICE_PUBLIC_KEY_LENGTH)
from src.receiver.receiver_loop import receiver_loop
@ -54,9 +56,9 @@ class TestReceiverLoop(unittest.TestCase):
ts_bytes = int_to_bytes(int(ts.strftime('%Y%m%d%H%M%S%f')[:-4]))
for key in queues:
packet = key + ts_bytes + bytes(ONION_SERVICE_PUBLIC_KEY_LENGTH)
encoded = rs.encode(packet)
broken_p = key + bytes.fromhex('df9005313af4136d') + bytes(ONION_SERVICE_PUBLIC_KEY_LENGTH)
packet = key + ts_bytes + bytes(ONION_SERVICE_PUBLIC_KEY_LENGTH)
encoded = rs.encode(packet)
broken_p = key + bytes.fromhex('df9005313af4136d') + bytes(ONION_SERVICE_PUBLIC_KEY_LENGTH)
broken_p += rs.encode(b'a')
def queue_delayer():

View File

@ -24,7 +24,11 @@ import unittest
from datetime import datetime
from unittest import mock
from src.common.statics import *
from src.common.statics import (BOLD_ON, CLEAR_ENTIRE_LINE, CLEAR_ENTIRE_SCREEN, CURSOR_LEFT_UP_CORNER,
CURSOR_UP_ONE_LINE, FILE, GROUP_ID_LENGTH, LOCAL_ID, NORMAL_TEXT,
ONION_SERVICE_PUBLIC_KEY_LENGTH, ORIGIN_CONTACT_HEADER, ORIGIN_USER_HEADER,
WIN_TYPE_COMMAND, WIN_TYPE_CONTACT, WIN_TYPE_FILE, WIN_TYPE_GROUP, WIN_UID_FILE,
WIN_UID_LOCAL)
from src.receiver.windows import RxWindow, WindowList
@ -35,6 +39,7 @@ from tests.utils import group_name_to_group_id, nick_to_pub_key, nick_to_
class TestRxWindow(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice', 'Bob', 'Charlie', LOCAL_ID])
self.group_list = GroupList(groups=['test_group', 'test_group2'])
self.settings = Settings()
@ -71,7 +76,14 @@ class TestRxWindow(TFCTestCase):
self.assertEqual(window.name, 'test_group')
def test_invalid_uid_raises_fr(self):
self.assert_fr("Invalid window 'bad_uid'.", self.create_window, 'bad_uid')
self.assert_fr("Invalid window 'mfqwcylbmfqwcylbmfqwcylbmfqwcylbmfqwcylbmfqwcylbmfqwbfad'.",
self.create_window, ONION_SERVICE_PUBLIC_KEY_LENGTH*b'a')
self.assert_fr("Invalid window '2dnAMoWNfTXAJ'.",
self.create_window, GROUP_ID_LENGTH*b'a')
self.assert_fr("Invalid window '<unable to encode>'.",
self.create_window, b'bad_uid')
def test_window_iterates_over_message_tuples(self):
# Setup
@ -373,6 +385,7 @@ testfile2.txt 15.0KB Charlie 7.00%
class TestWindowList(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
self.contact_list = ContactList(nicks=['Alice', 'Bob', 'Charlie', LOCAL_ID])
self.group_list = GroupList(groups=['test_group', 'test_group2'])

View File

@ -31,7 +31,13 @@ import requests
from src.common.crypto import X448
from src.common.db_onion import pub_key_to_onion_address, pub_key_to_short_address
from src.common.statics import *
from src.common.statics import (CONTACT_MGMT_QUEUE, CONTACT_REQ_QUEUE, C_REQ_MGMT_QUEUE, C_REQ_STATE_QUEUE,
DST_MESSAGE_QUEUE, EXIT, GROUP_ID_LENGTH, GROUP_MGMT_QUEUE,
GROUP_MSG_EXIT_GROUP_HEADER, GROUP_MSG_INVITE_HEADER, GROUP_MSG_JOIN_HEADER,
GROUP_MSG_MEMBER_ADD_HEADER, GROUP_MSG_MEMBER_REM_HEADER, GROUP_MSG_QUEUE,
MESSAGE_DATAGRAM_HEADER, ONION_SERVICE_PUBLIC_KEY_LENGTH, PUBLIC_KEY_DATAGRAM_HEADER,
RP_ADD_CONTACT_HEADER, RP_REMOVE_CONTACT_HEADER, TFC_PUBLIC_KEY_LENGTH, TOR_DATA_QUEUE,
UNIT_TEST_QUEUE, URL_TOKEN_QUEUE)
from src.relay.client import c_req_manager, client, client_scheduler, g_msg_manager, get_data_loop
@ -107,11 +113,13 @@ class TestClient(unittest.TestCase):
return TestClient.MockSession()
def setUp(self):
"""Pre-test actions."""
self.o_session = requests.session
self.queues = gen_queue_dict()
requests.session = TestClient.mock_session
def tearDown(self):
"""Post-test actions."""
requests.session = self.o_session
tear_queues(self.queues)
@ -243,11 +251,13 @@ class TestGetDataLoop(unittest.TestCase):
return TestGetDataLoop.Session()
def setUp(self):
"""Pre-test actions."""
self.o_session = requests.session
self.queues = gen_queue_dict()
requests.session = TestGetDataLoop.mock_session
def tearDown(self):
"""Post-test actions."""
requests.session = self.o_session
tear_queues(self.queues)

View File

@ -28,7 +28,11 @@ from unittest import mock
from unittest.mock import MagicMock
from src.common.encoding import int_to_bytes
from src.common.statics import *
from src.common.statics import (CLEAR_ENTIRE_SCREEN, CONTACT_MGMT_QUEUE, CURSOR_LEFT_UP_CORNER, C_REQ_MGMT_QUEUE,
C_REQ_STATE_QUEUE, EXIT, GROUP_MGMT_QUEUE, LOCAL_TESTING_PACKET_DELAY,
ONION_CLOSE_QUEUE, ONION_KEY_QUEUE, ONION_SERVICE_PRIVATE_KEY_LENGTH,
RP_ADD_CONTACT_HEADER, RP_REMOVE_CONTACT_HEADER, SRC_TO_RELAY_QUEUE,
UNENCRYPTED_SCREEN_CLEAR, WIPE)
from src.relay.commands import add_contact, add_onion_data, change_baudrate, change_ec_ratio, clear_windows, exit_tfc
from src.relay.commands import manage_contact_req, process_command, race_condition_delay, relay_command, remove_contact
@ -41,11 +45,13 @@ from tests.utils import gen_queue_dict, tear_queues, TFCTestCase
class TestRelayCommand(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.gateway = Gateway()
self.queues = gen_queue_dict()
self.gateway.settings.race_condition_delay = 0.0
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
@mock.patch('sys.stdin', MagicMock())
@ -64,10 +70,12 @@ class TestRelayCommand(unittest.TestCase):
class TestProcessCommand(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.gateway = Gateway()
self.queues = gen_queue_dict()
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_invalid_key(self):
@ -77,6 +85,7 @@ class TestProcessCommand(TFCTestCase):
class TestRaceConditionDelay(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.gateway = Gateway(local_testing_mode=True,
data_diode_sockets=True)
@ -89,6 +98,7 @@ class TestRaceConditionDelay(unittest.TestCase):
class TestClearWindows(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.gateway = Gateway(race_condition_delay=0.0)
def test_clear_display(self):
@ -106,10 +116,12 @@ class TestResetWindows(TFCTestCase):
class TestExitTFC(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.gateway = Gateway(race_condition_delay=0.0)
self.queues = gen_queue_dict()
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_exit_tfc(self):
@ -120,6 +132,7 @@ class TestExitTFC(unittest.TestCase):
class TestChangeECRatio(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.gateway = Gateway()
def test_non_digit_value_raises_fr(self):
@ -138,6 +151,7 @@ class TestChangeECRatio(TFCTestCase):
class TestChangeBaudrate(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.gateway = Gateway()
def test_non_digit_value_raises_fr(self):
@ -156,10 +170,12 @@ class TestChangeBaudrate(TFCTestCase):
class TestWipe(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.gateway = Gateway(race_condition_delay=0.0)
self.queues = gen_queue_dict()
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
@mock.patch('os.system', return_value=None)
@ -171,9 +187,11 @@ class TestWipe(unittest.TestCase):
class TestManageContactReq(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.queues = gen_queue_dict()
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_setting_management(self):
@ -186,68 +204,74 @@ class TestManageContactReq(unittest.TestCase):
class TestAddContact(unittest.TestCase):
def setUp(self):
self.queues = gen_queue_dict()
def setUp(self):
"""Pre-test actions."""
self.queues = gen_queue_dict()
def tearDown(self):
tear_queues(self.queues)
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_add_contact(self):
command = b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')])
def test_add_contact(self):
command = b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')])
self.assertIsNone(add_contact(command, True, self.queues))
self.assertEqual(self.queues[CONTACT_MGMT_QUEUE].qsize(), 1)
for q in [GROUP_MGMT_QUEUE, C_REQ_MGMT_QUEUE]:
command = self.queues[q].get()
self.assertEqual(command,
(RP_ADD_CONTACT_HEADER, b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')])))
self.assertEqual(self.queues[CONTACT_MGMT_QUEUE].get(),
(RP_ADD_CONTACT_HEADER, b''.join(list(map(nick_to_pub_key, ['Alice', 'Bob']))), True))
self.assertIsNone(add_contact(command, True, self.queues))
self.assertEqual(self.queues[CONTACT_MGMT_QUEUE].qsize(), 1)
for q in [GROUP_MGMT_QUEUE, C_REQ_MGMT_QUEUE]:
command = self.queues[q].get()
self.assertEqual(command,
(RP_ADD_CONTACT_HEADER, b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')])))
self.assertEqual(self.queues[CONTACT_MGMT_QUEUE].get(),
(RP_ADD_CONTACT_HEADER, b''.join(list(map(nick_to_pub_key, ['Alice', 'Bob']))), True))
class TestRemContact(unittest.TestCase):
def setUp(self):
self.queues = gen_queue_dict()
def setUp(self):
"""Pre-test actions."""
self.queues = gen_queue_dict()
def tearDown(self):
tear_queues(self.queues)
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_add_contact(self):
command = b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')])
def test_add_contact(self):
command = b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')])
self.assertIsNone(remove_contact(command, self.queues))
self.assertEqual(self.queues[CONTACT_MGMT_QUEUE].qsize(), 1)
self.assertEqual(self.queues[CONTACT_MGMT_QUEUE].get(),
(RP_REMOVE_CONTACT_HEADER,
b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')]),
False)
)
self.assertIsNone(remove_contact(command, self.queues))
self.assertEqual(self.queues[CONTACT_MGMT_QUEUE].qsize(), 1)
self.assertEqual(self.queues[CONTACT_MGMT_QUEUE].get(),
(RP_REMOVE_CONTACT_HEADER,
b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')]),
False)
)
for q in [GROUP_MGMT_QUEUE, C_REQ_MGMT_QUEUE]:
command = self.queues[q].get()
self.assertEqual(command, (RP_REMOVE_CONTACT_HEADER,
b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')])))
for q in [GROUP_MGMT_QUEUE, C_REQ_MGMT_QUEUE]:
command = self.queues[q].get()
self.assertEqual(command, (RP_REMOVE_CONTACT_HEADER,
b''.join([nick_to_pub_key('Alice'), nick_to_pub_key('Bob')])))
class TestAddOnionKey(unittest.TestCase):
def setUp(self):
self.queues = gen_queue_dict()
def setUp(self):
"""Pre-test actions."""
self.queues = gen_queue_dict()
def tearDown(self):
tear_queues(self.queues)
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_add_contact(self):
command = (ONION_SERVICE_PRIVATE_KEY_LENGTH * b'a'
+ b'b'
+ b'\x01'
+ int_to_bytes(1)
+ nick_to_pub_key('Alice')
+ nick_to_pub_key('Bob'))
self.assertIsNone(add_onion_data(command, self.queues))
self.assertEqual(self.queues[ONION_KEY_QUEUE].qsize(), 1)
self.assertEqual(self.queues[ONION_KEY_QUEUE].get(), (ONION_SERVICE_PRIVATE_KEY_LENGTH * b'a', b'b'))
def test_add_contact(self):
command = (ONION_SERVICE_PRIVATE_KEY_LENGTH * b'a'
+ b'b'
+ b'\x01'
+ int_to_bytes(1)
+ nick_to_pub_key('Alice')
+ nick_to_pub_key('Bob'))
self.assertIsNone(add_onion_data(command, self.queues))
self.assertEqual(self.queues[ONION_KEY_QUEUE].qsize(), 1)
self.assertEqual(self.queues[ONION_KEY_QUEUE].get(), (ONION_SERVICE_PRIVATE_KEY_LENGTH * b'a', b'b'))
if __name__ == '__main__':

View File

@ -30,7 +30,8 @@ from unittest.mock import MagicMock
import stem.control
from src.common.misc import validate_onion_addr
from src.common.statics import *
from src.common.statics import (EXIT, EXIT_QUEUE, ONION_CLOSE_QUEUE, ONION_KEY_QUEUE, ONION_SERVICE_PRIVATE_KEY_LENGTH,
TOR_DATA_QUEUE, TOR_SOCKS_PORT)
from src.relay.onion import get_available_port, onion_service, stem_compatible_ed25519_key_from_private_key, Tor
@ -44,6 +45,11 @@ class TestGetAvailablePort(unittest.TestCase):
port = get_available_port(1000, 65535)
self.assertEqual(port, 1234)
@mock.patch('builtins.open', mock.mock_open(read_data='TAILS_PRODUCT_NAME="Tails"'))
def test_port_is_tor_socket_port_when_running_on_tails(self):
port = get_available_port(1000, 65535)
self.assertEqual(port, TOR_SOCKS_PORT)
class TestTor(unittest.TestCase):
@ -52,7 +58,7 @@ class TestTor(unittest.TestCase):
def test_missing_binary_raises_critical_error(self, *_):
tor = Tor()
with self.assertRaises(SystemExit):
tor.connect('1234')
tor.connect(1234)
@mock.patch('time.sleep', return_value=None)
@mock.patch('stem.process.launch_tor_with_config', side_effect=[MagicMock(), OSError, MagicMock()])
@ -60,9 +66,9 @@ class TestTor(unittest.TestCase):
side_effect=['NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY="Done"', stem.SocketClosed])))
def test_closed_socket_raises_critical_error(self, *_):
tor = Tor()
self.assertIsNone(tor.connect('1234'))
self.assertIsNone(tor.connect(1234))
with self.assertRaises(SystemExit):
tor.connect('1234')
tor.connect(1234)
@mock.patch('time.sleep', return_value=None)
@mock.patch('time.monotonic', side_effect=[1, 20, 30, 40])
@ -72,7 +78,7 @@ class TestTor(unittest.TestCase):
@mock.patch('stem.process.launch_tor_with_config', return_value=MagicMock(poll=lambda: False))
def test_timeout_restarts_tor(self, *_):
tor = Tor()
self.assertIsNone(tor.connect('1234'))
self.assertIsNone(tor.connect(1234))
tor.stop()
@ -171,6 +177,33 @@ class TestOnionService(unittest.TestCase):
# Teardown
tear_queues(queues)
@mock.patch('stem.control.Controller.from_port', MagicMock())
@mock.patch('builtins.open', mock.mock_open(read_data='TAILS_PRODUCT_NAME="Tails"'))
def test_no_tor_process_is_created_when_tails_is_used(self, *_):
tor = Tor()
self.assertIsNone(tor.connect(1234))
self.assertIsNone(tor.tor_process)
@mock.patch('time.sleep', return_value=None)
def test_missing_tor_controller_raises_critical_error(self, *_):
# Setup
queues = gen_queue_dict()
orig_tor_connect = Tor.connect
Tor.connect = MagicMock(return_value=None)
controller = stem.control.Controller
controller.create_ephemeral_hidden_service = MagicMock()
queues[ONION_KEY_QUEUE].put((bytes(ONION_SERVICE_PRIVATE_KEY_LENGTH), b'\x01'))
# Test
with self.assertRaises(SystemExit):
onion_service(queues)
# Teardown
tear_queues(queues)
Tor.connect = orig_tor_connect
if __name__ == '__main__':
unittest.main(exit=False)

View File

@ -22,7 +22,7 @@ along with TFC. If not, see <https://www.gnu.org/licenses/>.
import unittest
from src.common.crypto import X448
from src.common.statics import *
from src.common.statics import CONTACT_REQ_QUEUE, F_TO_FLASK_QUEUE, M_TO_FLASK_QUEUE, URL_TOKEN_QUEUE
from src.relay.server import flask_server

View File

@ -28,7 +28,13 @@ from unittest import mock
from src.common.encoding import int_to_bytes
from src.common.reed_solomon import RSCodec
from src.common.statics import *
from src.common.statics import (COMMAND_DATAGRAM_HEADER, DST_COMMAND_QUEUE, DST_MESSAGE_QUEUE, EXIT,
FILE_DATAGRAM_HEADER, F_TO_FLASK_QUEUE, GATEWAY_QUEUE, GROUP_ID_LENGTH,
GROUP_MSG_EXIT_GROUP_HEADER, GROUP_MSG_INVITE_HEADER, GROUP_MSG_JOIN_HEADER,
GROUP_MSG_MEMBER_ADD_HEADER, GROUP_MSG_MEMBER_REM_HEADER,
LOCAL_KEY_DATAGRAM_HEADER, MESSAGE_DATAGRAM_HEADER, M_TO_FLASK_QUEUE,
PUBLIC_KEY_DATAGRAM_HEADER, SRC_TO_RELAY_QUEUE, TFC_PUBLIC_KEY_LENGTH,
UNENCRYPTED_DATAGRAM_HEADER, UNIT_TEST_QUEUE)
from src.relay.tcb import dst_outgoing, src_incoming
@ -39,6 +45,7 @@ from tests.utils import cd_unit_test, cleanup, gen_queue_dict, tear_queue
class TestSRCIncoming(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
self.unit_test_dir = cd_unit_test()
self.gateway = Gateway()
@ -48,6 +55,7 @@ class TestSRCIncoming(unittest.TestCase):
self.args = self.queues, self.gateway
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
cleanup(self.unit_test_dir)
@ -200,10 +208,11 @@ class TestDSTOutGoing(unittest.TestCase):
def queue_delayer():
"""Place packets into queue after delay."""
time.sleep(0.01)
time.sleep(0.015)
queues[DST_COMMAND_QUEUE].put(packet)
time.sleep(0.015)
queues[DST_MESSAGE_QUEUE].put(packet)
time.sleep(0.01)
time.sleep(0.015)
queues[UNIT_TEST_QUEUE].put(EXIT)
threading.Thread(target=queue_delayer).start()

View File

@ -28,9 +28,10 @@ from multiprocessing import Queue
from unittest import mock
from unittest.mock import MagicMock
from src.common.statics import DATA_FLOW, DST_LISTEN_SOCKET, EXIT, EXIT_QUEUE, IDLE, NCDCLR, NCDCRL, RP_LISTEN_SOCKET
from src.common.statics import SCNCLR, SCNCRL
from dd import animate, draw_frame, main, process_arguments, rx_loop, tx_loop
from src.common.statics import (DATA_FLOW, DST_LISTEN_SOCKET, EXIT, EXIT_QUEUE, IDLE,
NCDCLR, NCDCRL, RP_LISTEN_SOCKET, SCNCLR, SCNCRL)
from dd import animate, draw_frame, main, process_arguments, rx_loop, tx_loop
from tests.utils import tear_queue, TFCTestCase
@ -111,9 +112,11 @@ class TestAnimate(unittest.TestCase):
class TestRxLoop(unittest.TestCase):
def setUp(self) -> None:
"""Pre-test actions."""
self.queue = Queue()
def tearDown(self) -> None:
"""Post-test actions."""
tear_queue(self.queue)
@mock.patch('multiprocessing.connection.Listener', return_value=MagicMock(
@ -132,9 +135,11 @@ class TestRxLoop(unittest.TestCase):
class TestTxLoop(unittest.TestCase):
def setUp(self) -> None:
"""Pre-test actions."""
self.o_sleep = time.sleep
def tearDown(self) -> None:
"""Post-test actions."""
time.sleep = self.o_sleep
@mock.patch('time.sleep', lambda _: None)
@ -144,6 +149,7 @@ class TestTxLoop(unittest.TestCase):
queue = Queue()
def queue_delayer():
"""Place packet to queue after timer runs out."""
self.o_sleep(0.1)
queue.put(b'test_packet')
threading.Thread(target=queue_delayer).start()
@ -175,9 +181,11 @@ class TestProcessArguments(unittest.TestCase):
class TestMain(unittest.TestCase):
def setUp(self) -> None:
"""Pre-test actions."""
self.queue = Queue()
def tearDown(self) -> None:
"""Post-test actions."""
tear_queue(self.queue)
@mock.patch('time.sleep', lambda _: None)
@ -187,6 +195,7 @@ class TestMain(unittest.TestCase):
queues = {EXIT_QUEUE: self.queue}
def queue_delayer():
"""Place packet to queue after timer runs out."""
time.sleep(0.1)
queues[EXIT_QUEUE].put(EXIT)
threading.Thread(target=queue_delayer).start()

View File

@ -27,7 +27,13 @@ from unittest.mock import MagicMock
from src.common.db_logs import write_log_entry
from src.common.encoding import bool_to_bytes
from src.common.statics import *
from src.common.statics import (BOLD_ON, CLEAR_ENTIRE_SCREEN, COMMAND_PACKET_QUEUE, CURSOR_LEFT_UP_CORNER,
DIR_USER_DATA, KEX_STATUS_NO_RX_PSK, KEX_STATUS_UNVERIFIED, KEX_STATUS_VERIFIED,
KEY_MANAGEMENT_QUEUE, LOGFILE_MASKING_QUEUE, LOG_ENTRY_LENGTH, MESSAGE,
MESSAGE_PACKET_QUEUE, M_S_HEADER, NORMAL_TEXT, PADDING_LENGTH, PRIVATE_MESSAGE_HEADER,
RELAY_PACKET_QUEUE, RESET, SENDER_MODE_QUEUE, TM_COMMAND_PACKET_QUEUE,
TRAFFIC_MASKING_QUEUE, TX, UNENCRYPTED_DATAGRAM_HEADER, UNENCRYPTED_WIPE_COMMAND,
VERSION, WIN_TYPE_CONTACT, WIN_TYPE_GROUP)
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
@ -44,6 +50,7 @@ from tests.utils import gen_queue_dict, nick_to_onion_address, nick_to_pu
class TestProcessCommand(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.window = TxWindow()
self.contact_list = ContactList()
self.group_list = GroupList()
@ -56,6 +63,7 @@ class TestProcessCommand(TFCTestCase):
self.queues, self.master_key, self.onion_service, self.gateway)
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_valid_command(self):
@ -84,12 +92,14 @@ class TestPrintAbout(TFCTestCase):
class TestClearScreens(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.window = TxWindow(uid=nick_to_pub_key('Alice'))
self.settings = Settings()
self.queues = gen_queue_dict()
self.args = self.window, self.settings, self.queues
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
@mock.patch('os.system', return_value=None)
@ -130,6 +140,7 @@ class TestClearScreens(unittest.TestCase):
class TestRXPShowSysWin(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.window = TxWindow(name='Alice',
uid=nick_to_pub_key('Alice'))
self.settings = Settings()
@ -137,6 +148,7 @@ class TestRXPShowSysWin(unittest.TestCase):
self.args = self.window, self.settings, self.queues
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
@mock.patch('builtins.input', side_effect=['', EOFError, KeyboardInterrupt])
@ -161,12 +173,14 @@ class TestRXPShowSysWin(unittest.TestCase):
class TestExitTFC(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings(local_testing_mode=True)
self.queues = gen_queue_dict()
self.gateway = Gateway(data_diode_sockets=True)
self.args = self.settings, self.queues, self.gateway
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
@mock.patch('time.sleep', return_value=None)
@ -197,17 +211,19 @@ class TestLogCommand(TFCTestCase):
@mock.patch("getpass.getpass", return_value='test_password')
def setUp(self, _):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.window = TxWindow(name='Alice',
uid=nick_to_pub_key('Alice'))
self.window = TxWindow(name='Alice', uid=nick_to_pub_key('Alice'))
self.contact_list = ContactList()
self.group_list = GroupList()
self.settings = Settings()
self.queues = gen_queue_dict()
self.master_key = MasterKey()
self.args = self.window, self.contact_list, self.group_list, self.settings, self.queues, self.master_key
self.args = (self.window, self.contact_list, self.group_list,
self.settings, self.queues, self.master_key)
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
tear_queues(self.queues)
@ -252,7 +268,8 @@ class TestLogCommand(TFCTestCase):
@mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 0.1)
@mock.patch('src.common.db_masterkey.MAX_KEY_DERIVATION_TIME', 1.0)
@mock.patch('os.popen', return_value=MagicMock(read=MagicMock(return_value=MagicMock(splitlines=MagicMock(return_value=["MemFree 10240"])))))
@mock.patch('os.popen', return_value=MagicMock(
read=MagicMock(return_value=MagicMock(splitlines=MagicMock(return_value=["MemAvailable 10240"])))))
@mock.patch("multiprocessing.cpu_count", return_value=1)
@mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', return_value='Yes')
@ -263,10 +280,10 @@ class TestLogCommand(TFCTestCase):
self.assert_fr("Log file export aborted.",
log_command, UserInput('export'), *self.args)
@mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 0.1)
@mock.patch('src.common.db_masterkey.MAX_KEY_DERIVATION_TIME', 1.0)
@mock.patch('os.popen', return_value=MagicMock(read=MagicMock(return_value=MagicMock(splitlines=MagicMock(return_value=["MemFree 10240"])))))
@mock.patch('os.popen', return_value=MagicMock(
read=MagicMock(return_value=MagicMock(splitlines=MagicMock(return_value=["MemAvailable 10240"])))))
@mock.patch("multiprocessing.cpu_count", return_value=1)
@mock.patch("getpass.getpass", side_effect=3*['test_password'] + ['invalid_password'] + ['test_password'])
@mock.patch('time.sleep', return_value=None)
@ -293,6 +310,7 @@ class TestSendOnionServiceKey(TFCTestCase):
confirmation_code = b'a'
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList()
self.settings = Settings()
self.onion_service = OnionService()
@ -334,6 +352,7 @@ class TestSendOnionServiceKey(TFCTestCase):
class TestPrintHelp(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
self.settings.traffic_masking = False
@ -466,6 +485,7 @@ Shift + PgUp/PgDn Scroll terminal up/down
class TestPrintRecipients(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice', 'Bob'])
self.group_list = GroupList(groups=['test_group', 'test_group_2'])
self.args = self.contact_list, self.group_list
@ -477,6 +497,7 @@ class TestPrintRecipients(TFCTestCase):
class TestChangeMasterKey(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.contact_list = ContactList()
self.group_list = GroupList()
@ -489,6 +510,7 @@ class TestChangeMasterKey(TFCTestCase):
self.queues, self.master_key, self.onion_service)
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
tear_queues(self.queues)
@ -508,7 +530,7 @@ class TestChangeMasterKey(TFCTestCase):
self.assert_fr("Error: Invalid target system 't'.",
change_master_key, UserInput("passwd t"), *self.args)
@mock.patch('os.popen', return_value=MagicMock(read=MagicMock(return_value='foo\nMemFree 200')))
@mock.patch('os.popen', return_value=MagicMock(read=MagicMock(return_value='foo\nMemAvailable 200')))
@mock.patch('getpass.getpass', return_value='a')
@mock.patch('time.sleep', return_value=None)
@mock.patch('src.common.db_masterkey.MIN_KEY_DERIVATION_TIME', 0.01)
@ -536,6 +558,7 @@ class TestChangeMasterKey(TFCTestCase):
class TestRemoveLog(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.contact_list = ContactList(nicks=['Alice'])
self.group_list = GroupList(groups=['test_group'])
@ -546,6 +569,7 @@ class TestRemoveLog(TFCTestCase):
self.args = self.contact_list, self.group_list, self.settings, self.queues, self.master_key
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
cleanup(self.unit_test_dir)
@ -641,6 +665,7 @@ class TestRemoveLog(TFCTestCase):
class TestChangeSetting(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.window = TxWindow()
self.contact_list = ContactList()
self.group_list = GroupList()
@ -648,10 +673,11 @@ class TestChangeSetting(TFCTestCase):
self.queues = gen_queue_dict()
self.master_key = MasterKey()
self.gateway = Gateway()
self.args = self.window, self.contact_list, self.group_list, \
self.settings, self.queues, self.master_key, self.gateway
self.args = (self.window, self.contact_list, self.group_list,
self.settings, self.queues, self.master_key, self.gateway)
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_missing_setting_raises_fr(self):
@ -841,9 +867,11 @@ serial_error_correction 5 5 Number of byte
class TestRxPDisplayUnread(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.queues = gen_queue_dict()
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_command(self):
@ -854,6 +882,7 @@ class TestRxPDisplayUnread(unittest.TestCase):
class TestVerify(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.window = TxWindow(uid=nick_to_pub_key("Alice"),
name='Alice',
window_contacts=[create_contact('test_group')],
@ -894,6 +923,7 @@ class TestVerify(TFCTestCase):
class TestWhisper(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.window = TxWindow(uid=nick_to_pub_key("Alice"),
name='Alice',
window_contacts=[create_contact('Alice')],
@ -918,6 +948,7 @@ class TestWhisper(TFCTestCase):
class TestWhois(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice'])
self.group_list = GroupList(groups=['test_group'])
self.args = self.contact_list, self.group_list
@ -960,6 +991,7 @@ class TestWhois(TFCTestCase):
class TestWipe(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
self.queues = gen_queue_dict()
self.gateway = Gateway()

View File

@ -24,7 +24,8 @@ import unittest
from unittest import mock
from src.common.encoding import b58encode
from src.common.statics import *
from src.common.statics import (COMMAND_PACKET_QUEUE, GROUP_ID_LENGTH, RELAY_PACKET_QUEUE,
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 process_group_command, group_rename
@ -36,6 +37,7 @@ from tests.utils import cd_unit_test, cleanup, gen_queue_dict, nick_to_pu
class TestProcessGroupCommand(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice'])
self.group_list = GroupList()
self.settings = Settings()
@ -44,12 +46,13 @@ class TestProcessGroupCommand(TFCTestCase):
self.args = self.contact_list, self.group_list, self.settings, self.queues, self.settings
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_raises_fr_when_traffic_masking_is_enabled(self):
# Setup
self.settings.traffic_masking = True
# Test
self.assert_fr("Error: Command is disabled during traffic masking.",
process_group_command, UserInput(), *self.args)
@ -80,6 +83,7 @@ class TestProcessGroupCommand(TFCTestCase):
class TestGroupCreate(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice', 'Bob'])
self.group_list = GroupList()
self.settings = Settings()
@ -89,15 +93,16 @@ class TestGroupCreate(TFCTestCase):
self.args = self.contact_list, self.group_list, self.settings, self.queues, self.settings
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def configure_groups(self, no_contacts: int) -> None:
"""Configure group list."""
self.contact_list = ContactList(nicks=[str(n) for n in range(no_contacts)])
self.group_list = GroupList(groups=['test_group'])
self.group = self.group_list.get_group('test_group')
self.group.members = self.contact_list.contacts
self.account_list = [nick_to_pub_key(str(n)) for n in range(no_contacts)]
self.contact_list = ContactList(nicks=[str(n) for n in range(no_contacts)])
self.group_list = GroupList(groups=['test_group'])
self.group = self.group_list.get_group('test_group')
self.group.members = self.contact_list.contacts
self.account_list = [nick_to_pub_key(str(n)) for n in range(no_contacts)]
def test_invalid_group_name_raises_fr(self):
# Setup
@ -142,6 +147,7 @@ class TestGroupCreate(TFCTestCase):
class TestGroupAddMember(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.user_input = UserInput()
self.contact_list = ContactList(nicks=['Alice', 'Bob'])
self.group_list = GroupList()
@ -151,6 +157,7 @@ class TestGroupAddMember(TFCTestCase):
self.args = self.contact_list, self.group_list, self.settings, self.queues, self.settings
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def configure_groups(self, no_contacts: int) -> None:
@ -206,6 +213,7 @@ class TestGroupAddMember(TFCTestCase):
class TestGroupRmMember(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.user_input = UserInput()
self.contact_list = ContactList(nicks=['Alice', 'Bob'])
@ -216,6 +224,7 @@ class TestGroupRmMember(TFCTestCase):
self.args = self.contact_list, self.group_list, self.settings, self.queues, self.settings
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
tear_queues(self.queues)
@ -239,6 +248,7 @@ class TestGroupRmMember(TFCTestCase):
class TestGroupRmGroup(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.user_input = UserInput()
self.contact_list = ContactList(nicks=['Alice', 'Bob'])
@ -249,6 +259,7 @@ class TestGroupRmGroup(TFCTestCase):
self.args = self.contact_list, self.group_list, self.settings, self.queues, self.settings
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
tear_queues(self.queues)
@ -280,6 +291,7 @@ class TestGroupRmGroup(TFCTestCase):
class TestGroupRename(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.queues = gen_queue_dict()
self.settings = Settings()
self.contact_list = ContactList()
@ -288,6 +300,7 @@ class TestGroupRename(TFCTestCase):
self.args = self.window, self.contact_list, self.group_list, self.settings, self.queues
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_contact_window_raises_fr(self):

View File

@ -25,7 +25,9 @@ import unittest
from unittest import mock
from src.common.crypto import blake2b
from src.common.statics import *
from src.common.statics import (COMMAND_PACKET_QUEUE, CONFIRM_CODE_LENGTH, FINGERPRINT_LENGTH, KDB_REMOVE_ENTRY_HEADER,
KEY_MANAGEMENT_QUEUE, LOG_SETTING_QUEUE, RELAY_PACKET_QUEUE, TM_COMMAND_PACKET_QUEUE,
WIN_TYPE_CONTACT, WIN_TYPE_GROUP)
from src.transmitter.contact import add_new_contact, change_nick, contact_setting, remove_contact
@ -38,6 +40,7 @@ from tests.utils import nick_to_onion_address, nick_to_pub_key, tear_queu
class TestAddNewContact(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList()
self.group_list = GroupList()
self.settings = Settings(disable_gui_dialog=True)
@ -46,6 +49,7 @@ class TestAddNewContact(TFCTestCase):
self.args = self.contact_list, self.group_list, self.settings, self.queues, self.onion_service
def tearDown(self):
"""Post-test actions."""
with ignored(OSError):
os.remove(f'v4dkh.psk - Give to hpcra')
tear_queues(self.queues)
@ -76,9 +80,9 @@ class TestAddNewContact(TFCTestCase):
self.assertNotEqual(contact.tx_fingerprint, bytes(FINGERPRINT_LENGTH))
@mock.patch('src.transmitter.key_exchanges.ARGON2_PSK_MEMORY_COST', 200)
@mock.patch('src.transmitter.key_exchanges.MIN_KEY_DERIVATION_TIME', 0.1)
@mock.patch('src.transmitter.key_exchanges.MIN_KEY_DERIVATION_TIME', 1.0)
@mock.patch('builtins.input', side_effect=[nick_to_onion_address("Alice"), 'Alice_', 'psk', '.'])
@mock.patch('src.common.statics.MIN_KEY_DERIVATION_TIME', 0.1)
@mock.patch('src.common.statics.MAX_KEY_DERIVATION_TIME', 1.0)
@mock.patch('builtins.input', side_effect=[nick_to_onion_address("Alice"), 'Alice_', 'psk', '.', '', 'ff', 'fc'])
@mock.patch('getpass.getpass', return_value='test_password')
@mock.patch('time.sleep', return_value=None)
def test_standard_nick_psk_kex(self, *_):
@ -97,6 +101,7 @@ class TestAddNewContact(TFCTestCase):
class TestRemoveContact(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.contact_list = ContactList(nicks=['Alice'])
self.group_list = GroupList(groups=['test_group'])
@ -107,6 +112,7 @@ class TestRemoveContact(TFCTestCase):
self.args = self.contact_list, self.group_list, self.settings, self.queues, self.master_key
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
tear_queues(self.queues)
@ -216,6 +222,7 @@ class TestRemoveContact(TFCTestCase):
class TestChangeNick(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice'])
self.group_list = GroupList()
self.settings = Settings()
@ -223,6 +230,7 @@ class TestChangeNick(TFCTestCase):
self.args = self.contact_list, self.group_list, self.settings, self.queues
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_missing_nick_raises_fr(self):
@ -235,7 +243,8 @@ class TestChangeNick(TFCTestCase):
contact=create_contact('Bob'))
# Test
self.assert_fr("Error: Nick must be printable.", change_nick, UserInput("nick Alice\x01"), window, *self.args)
self.assert_fr("Error: Nick must be printable.",
change_nick, UserInput("nick Alice\x01"), window, *self.args)
def test_no_contact_raises_fr(self):
# Setup
@ -244,7 +253,8 @@ class TestChangeNick(TFCTestCase):
window.contact = None
# Test
self.assert_fr("Error: Window does not have contact.", change_nick, UserInput("nick Alice\x01"), window, *self.args)
self.assert_fr("Error: Window does not have contact.",
change_nick, UserInput("nick Alice\x01"), window, *self.args)
def test_successful_nick_change(self):
# Setup
@ -274,6 +284,7 @@ class TestChangeNick(TFCTestCase):
class TestContactSetting(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList(nicks=['Alice', 'Bob'])
self.group_list = GroupList(groups=['test_group'])
self.settings = Settings()
@ -282,6 +293,7 @@ class TestContactSetting(TFCTestCase):
self.args = self.contact_list, self.group_list, self.settings, self.queues
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_invalid_command_raises_fr(self):

View File

@ -31,12 +31,14 @@ from tests.utils import cd_unit_test, cleanup, TFCTestCase
class TestFile(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.window = TxWindow()
self.settings = Settings()
self.args = self.window, self.settings
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
def test_missing_file_raises_fr(self):

View File

@ -25,7 +25,7 @@ from unittest import mock
from unittest.mock import MagicMock
from src.common.crypto import blake2b
from src.common.statics import *
from src.common.statics import CONFIRM_CODE_LENGTH
from src.transmitter.input_loop import input_loop
@ -51,6 +51,7 @@ class TestInputLoop(unittest.TestCase):
'/exit'] # Enter exit command
def setUp(self):
"""Pre-test actions."""
self.settings = Settings(disable_gui_dialog=True)
self.gateway = Gateway()
self.contact_list = ContactList()
@ -60,6 +61,7 @@ class TestInputLoop(unittest.TestCase):
self.queues = gen_queue_dict()
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
@mock.patch('builtins.input', side_effect=input_list)

View File

@ -26,7 +26,11 @@ from unittest import mock
from src.common.crypto import blake2b
from src.common.encoding import b58encode
from src.common.statics import *
from src.common.statics import (COMMAND_PACKET_QUEUE, CONFIRM_CODE_LENGTH, ECDHE, FINGERPRINT_LENGTH,
KDB_ADD_ENTRY_HEADER, KEX_STATUS_HAS_RX_PSK, KEX_STATUS_NO_RX_PSK, KEX_STATUS_PENDING,
KEX_STATUS_UNVERIFIED, KEX_STATUS_VERIFIED, KEY_MANAGEMENT_QUEUE, LOCAL_ID, LOCAL_NICK,
LOCAL_PUBKEY, RELAY_PACKET_QUEUE, SYMMETRIC_KEY_LENGTH, TFC_PUBLIC_KEY_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 rxp_load_psk, start_key_exchange, verify_fingerprints
@ -39,6 +43,7 @@ from tests.utils import nick_to_short_address, tear_queues, TFCTestCase,
class TestOnionService(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList()
self.settings = Settings()
self.onion_service = OnionService()
@ -56,12 +61,14 @@ class TestOnionService(TFCTestCase):
class TestLocalKey(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList()
self.settings = Settings()
self.queues = gen_queue_dict()
self.args = self.contact_list, self.settings, self.queues
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_new_local_key_when_traffic_masking_is_enabled_raises_fr(self):
@ -127,12 +134,14 @@ class TestVerifyFingerprints(unittest.TestCase):
class TestKeyExchange(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.contact_list = ContactList()
self.settings = Settings()
self.queues = gen_queue_dict()
self.args = self.contact_list, self.settings, self.queues
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
@mock.patch('shutil.get_terminal_size', return_value=[200, 200])
@ -143,7 +152,7 @@ class TestKeyExchange(TFCTestCase):
@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))
def test_invalid_public_key_length_raises_fr(self, *_):
self.assert_fr("Error: Invalid public key length",
self.assert_fr("Error: Invalid public key length",
start_key_exchange, nick_to_pub_key("Alice"), 'Alice', *self.args)
@mock.patch('builtins.input', side_effect=['', # Empty message should resend key
@ -235,14 +244,16 @@ class TestKeyExchange(TFCTestCase):
class TestPSK(TFCTestCase):
def setUp(self):
self.unit_test_dir = cd_unit_test()
self.contact_list = ContactList()
self.settings = Settings(disable_gui_dialog=True)
self.queues = gen_queue_dict()
self.onion_service = OnionService()
self.args = self.contact_list, self.settings, self.onion_service, self.queues
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.contact_list = ContactList()
self.settings = Settings(disable_gui_dialog=True)
self.queues = gen_queue_dict()
self.onion_service = OnionService()
self.args = self.contact_list, self.settings, self.onion_service, self.queues
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
with ignored(OSError):
@ -250,7 +261,7 @@ class TestPSK(TFCTestCase):
tear_queues(self.queues)
@mock.patch('builtins.input', side_effect=['/root/', '.'])
@mock.patch('builtins.input', side_effect=['/root/', '.', 'fc'])
@mock.patch('time.sleep', return_value=None)
@mock.patch('getpass.getpass', return_value='test_password')
@mock.patch('src.transmitter.key_exchanges.ARGON2_PSK_MEMORY_COST', 1000)
@ -292,11 +303,13 @@ class TestPSK(TFCTestCase):
class TestReceiverLoadPSK(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
self.queues = gen_queue_dict()
self.args = self.settings, self.queues
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_raises_fr_when_traffic_masking_is_enabled(self):
@ -324,7 +337,7 @@ class TestReceiverLoadPSK(TFCTestCase):
# Test
self.assert_fr(f"Error: The current key was exchanged with {ECDHE}.",
rxp_load_psk, window, contact_list, *self.args)
@mock.patch('src.transmitter.key_exchanges.ARGON2_PSK_MEMORY_COST', 1000)
@mock.patch('src.transmitter.key_exchanges.ARGON2_PSK_TIME_COST', 0.01)
@mock.patch('time.sleep', return_value=None)

View File

@ -27,7 +27,12 @@ import unittest
from multiprocessing import Queue
from unittest import mock
from src.common.statics import *
from src.common.statics import (ASSEMBLY_PACKET_LENGTH, COMMAND, COMMAND_PACKET_QUEUE, C_A_HEADER, C_E_HEADER,
C_L_HEADER, C_S_HEADER, FILE, F_A_HEADER, F_E_HEADER, F_L_HEADER, F_S_HEADER,
GROUP_MSG_INVITE_HEADER, LOCAL_ID, MESSAGE, MESSAGE_PACKET_QUEUE, M_A_HEADER,
M_E_HEADER, M_L_HEADER, M_S_HEADER, RELAY_PACKET_QUEUE, SYMMETRIC_KEY_LENGTH,
TM_COMMAND_PACKET_QUEUE, TM_FILE_PACKET_QUEUE, TM_MESSAGE_PACKET_QUEUE,
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 send_file, send_packet, split_to_assembly_packets
@ -40,11 +45,13 @@ from tests.utils import cd_unit_test, cleanup, gen_queue_dict, tear_queue
class TestQueueMessage(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.queues = gen_queue_dict()
self.settings = Settings()
self.args = self.settings, self.queues
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_private_message_header(self):
@ -84,15 +91,17 @@ class TestQueueMessage(unittest.TestCase):
class TestSendFile(TFCTestCase):
def setUp(self):
self.unit_test_dir = cd_unit_test()
self.settings = Settings()
self.queues = gen_queue_dict()
self.window = TxWindow()
self.onion_service = OnionService()
self.contact_list = ContactList(nicks=['Alice', 'Bob', 'Charlie'])
self.args = self.settings, self.queues, self.window
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.settings = Settings()
self.queues = gen_queue_dict()
self.window = TxWindow()
self.onion_service = OnionService()
self.contact_list = ContactList(nicks=['Alice', 'Bob', 'Charlie'])
self.args = self.settings, self.queues, self.window
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
tear_queues(self.queues)
@ -149,10 +158,12 @@ class TestQueueFile(TFCTestCase):
'rx_serial_settings.json', 'tx_onion_db')
def setUp(self):
"""Pre-test actions."""
self.unit_test_dir = cd_unit_test()
self.queues = gen_queue_dict()
self.queues = gen_queue_dict()
def tearDown(self):
"""Post-test actions."""
cleanup(self.unit_test_dir)
tear_queues(self.queues)
@ -266,10 +277,12 @@ class TestQueueFile(TFCTestCase):
class TestQueueCommand(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
self.queues = gen_queue_dict()
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_queue_command(self):
@ -323,14 +336,16 @@ class TestSplitToAssemblyPackets(unittest.TestCase):
class TestQueueAssemblyPackets(unittest.TestCase):
def setUp(self):
"""Pre-test actions."""
self.settings = Settings()
self.queues = gen_queue_dict()
self.window = TxWindow(uid=nick_to_pub_key("Alice"),
log_messages=True)
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):
"""Post-test actions."""
tear_queues(self.queues)
def test_queue_message_traffic_masking(self):
@ -425,6 +440,7 @@ class TestSendPacket(unittest.TestCase):
"""
def setUp(self):
"""Pre-test actions."""
self.l_queue = Queue()
self.key_list = KeyList(nicks=['Alice'])
self.settings = Settings()
@ -432,6 +448,7 @@ class TestSendPacket(unittest.TestCase):
self.onion_service = OnionService()
def tearDown(self):
"""Post-test actions."""
tear_queue(self.l_queue)
def test_message_length(self):
@ -497,9 +514,11 @@ class TestSendPacket(unittest.TestCase):
class TestCancelPacket(TFCTestCase):
def setUp(self):
"""Pre-test actions."""
self.queues = gen_queue_dict()
def tearDown(self):
"""Post-test actions."""
tear_queues(self.queues)
def test_cancel_message_during_normal(self):

Some files were not shown because too many files have changed in this diff Show More