This commit is contained in:
Markus Ottela 2020-10-09 03:28:03 +03:00
parent f367a902ac
commit 6ae64aa802
28 changed files with 423 additions and 478 deletions

View File

@ -153,7 +153,7 @@ Receiver Program then authenticates, decrypts and processes the received message
When Bob responds, he will type his message to the Transmitter Program on his Source
Computer, and after a mirrored process, Alice reads the message from the Receiver Program
on her Destination Computer.
on her Destination Computer. All this happens seamlessly and automatically.
### Why keys and plaintexts cannot be exfiltrated
@ -179,9 +179,11 @@ the data flow constraints introduced by the data diode; To allow key exchanges,
elliptic-curve public keys are input manually by the user.
3. The Networked Computer is designed under the assumption it can be compromised by a
remote attacker: All sensitive data that passes through the Relay Program is encrypted and
signed with no exceptions. Since the attacker is unable to exfiltrate decryption keys from
the Source or Destination Computer, the ciphertexts are of no value to the attacker.
remote attacker: All sensitive data that passes through the Relay Program is protected by
[authenticated encryption](https://en.wikipedia.org/wiki/Authenticated_encryption)
with no exceptions. Since the attacker is unable to exfiltrate decryption keys from
the Source or Destination Computer, the ciphertexts obtained from Networked Computer
are of no value to the attacker.
![](https://www.cs.helsinki.fi/u/oottela/wiki/readme/attacks.png)
@ -208,21 +210,21 @@ hardware data diode is needed.
### Supported Operating Systems
#### Source/Destination Computer
- Debian 10
- Debian 10.6
- PureOS 9.0
- *buntu 20.04 LTS
- Linux Mint 20
- LMDE 4
- Qubes 4 (Debian 10 VM)
- Qubes 4.0.3 (Debian 10 VM)
#### Networked Computer
- Tails 4.8
- Debian 10
- Tails 4.11
- Debian 10.6
- PureOS 9.0
- *buntu 20.04 LTS
- Linux Mint 20
- LMDE 4
- Qubes 4 (Debian 10 VM)
- Qubes 4.0.3 (Debian 10 VM)
### More information

View File

@ -28,17 +28,17 @@ INSTALL_DIR="/opt/tfc"
APPDIRS=appdirs-1.4.4-py2.py3-none-any.whl
ARGON2_CFFI=argon2_cffi-20.1.0-cp35-abi3-manylinux1_x86_64.whl
CERTIFI=certifi-2020.6.20-py2.py3-none-any.whl
CFFI37=cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl
CFFI38=cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl
CFFI37=cffi-1.14.3-cp37-cp37m-manylinux1_x86_64.whl
CFFI38=cffi-1.14.3-cp38-cp38-manylinux1_x86_64.whl
CHARDET=chardet-3.0.4-py2.py3-none-any.whl
CLICK=click-7.1.2-py2.py3-none-any.whl
CRYPTOGRAPHY37=cryptography-2.9.2-cp35-abi3-manylinux1_x86_64.whl
CRYPTOGRAPHY38=cryptography-2.9.2-cp35-abi3-manylinux2010_x86_64.whl
CRYPTOGRAPHY37=cryptography-3.1.1-cp35-abi3-manylinux1_x86_64.whl
CRYPTOGRAPHY38=cryptography-3.1.1-cp35-abi3-manylinux2010_x86_64.whl
DISTLIB=distlib-0.3.1-py2.py3-none-any.whl
FILELOCK=filelock-3.0.12-py3-none-any.whl
FLASK=Flask-1.1.2-py2.py3-none-any.whl
IDNA=idna-2.10-py2.py3-none-any.whl
IMPORTLIB_METADATA=importlib_metadata-1.7.0-py2.py3-none-any.whl
IMPORTLIB_METADATA=importlib_metadata-2.0.0-py2.py3-none-any.whl
ITSDANGEROUS=itsdangerous-1.1.0-py2.py3-none-any.whl
JINJA2=Jinja2-2.11.2-py2.py3-none-any.whl
MARKUPSAFE=MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl
@ -47,12 +47,12 @@ PYNACL=PyNaCl-1.4.0-cp35-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.24.0-py2.py3-none-any.whl
SETUPTOOLS=setuptools-47.3.1-py3-none-any.whl
SETUPTOOLS=setuptools-50.3.0-py3-none-any.whl
SIX=six-1.15.0-py2.py3-none-any.whl
URLLIB3=urllib3-1.25.9-py2.py3-none-any.whl
VIRTUALENV=virtualenv-20.0.25-py2.py3-none-any.whl
URLLIB3=urllib3-1.25.10-py2.py3-none-any.whl
VIRTUALENV=virtualenv-20.0.33-py2.py3-none-any.whl
WERKZEUG=Werkzeug-1.0.1-py2.py3-none-any.whl
ZIPP=zipp-3.1.0-py3-none-any.whl
ZIPP=zipp-3.3.0-py3-none-any.whl
# ----------------------------------------------------------------------------------------
@ -63,16 +63,16 @@ DIGEST_PYSERIAL=8333ac2843fd136d5d0d63b527b37866f7d18afc3bb33c4938b63af077492aeb
DIGEST_PYSOCKS=313b954102231d038d52ab58f41e3642579be29f827135b8dd92c06acb362effcb0a7fd5f35de9273372b92d9fe29f38381ae44f8b41aa90d2564d6dd07ecd12
# Virtualenv
DIGEST_ZIPP=89170b91cfdc0ef4d85b5316b484c8d6e01985f19bb9f545b11d648e122392efa68d40c66e056b8998fb69af49f4e18707f783be8d500b8957ce3a885662d27c
DIGEST_ZIPP=1c83f8958eb172083a42c3cd0745e0c32def319992dfb227b906a9d0a9fd6f9dd556de49a8a84f76e27b0ebce7abe6a9a7fd12ae532e86898046c68e68d28f11
DIGEST_FILELOCK=d13edd50779bca9842694e0da157ca1fdad9d28166771275049f41dea4b8d8466fc5604b610b6ad64552cdf4c1d3cada9977ca37c6b775c4cc92f333709e8ea3
DIGEST_IMPORTLIB_METADATA=7146604e980d7921af3fd89351edba9919e2ff93879676adda7b1c55804b2d4b8cc6fbbd4064b5d03b5bc89a6a968b446f438deeb117412e140a676f05a785f8
DIGEST_IMPORTLIB_METADATA=09ec4c718781e3ba6ed8024a094081ce530f30c1aa7df8f10729d64f17839bcc35dc3c94218209dbb6d133f3052d33cebe282bf7b53ba9646d1653ce62cdae3b
DIGEST_SIX=0416d59434623604de755601c919722c2b800042612a2a7b221ecd3ccf556aca3a78f0f926fd640032a3d74d153457628a89c25065dfcdbb96892d5bf7279904
DIGEST_DISTLIB=ac65d35a5309ec22db5b1e9ab6c20014084feab11e86e81bee6d0bfcc65940dfdcaa2711ac1e98c1ef179b110a4ea03dbaf042b894d3051da9d339c534664e00
DIGEST_APPDIRS=8e6c1ea544013ea2567cda2d8b8c7b441bc50ac689aa7f95de67e3795aa083e9592c687d74fdbb37f5a75e0beab398fe47df5bced14ee9c204cfe5ecc364ef44
DIGEST_VIRTUALENV=812cc4b096e4357936d94c0e4f768e943eaf3b5ce1edd5ca309fc4433a3bf03ee7385cdeaf1a277408d250ecf28eb0e1d871da0818cf764d65109be42007e94e
DIGEST_VIRTUALENV=c15f4b8d4df895ba03ecc19631198584c97e74b3026b7e82207e62d932ea03bb4c5dd36349764c1418021aa6611072a44c9b3166bbf7040cfc5815efba03b2d1
# Requests
DIGEST_URLLIB3=b20687b4ce06164c5b932b43c5b758efd864668ee2b60f6cd6ce6c27f0ea16b9d1222ec0c061618fc3f0de362c0f18be95864bd91ecaa73fdfa92bd666fb4378
DIGEST_URLLIB3=b4e88397d5ac77c669c8bf01fdf6a70ec7cc3acbdf69e0113ce9a5cd8394e932e6e153d85c60978917e8bf6e85e15f1a4c7da10d2a10cfa0735884a5861a981d
DIGEST_IDNA=7b7be129e1a99288aa74a15971377cb17bee1618843c03c8f782e287d0f3ecf3b8f26e3ea736444eb358f1d6079131a7eb291446f3279874eb8e00b624d9471c
DIGEST_CHARDET=bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4
DIGEST_CERTIFI=960f1cbe72443230ecba527b5bc4bb8a45a33feb646b0ad01dcb606b9ec3729d27dff5cfa04655d92efd4dec691d61c62d80f8fd39a82fc21528727eeb5c9991
@ -88,11 +88,11 @@ DIGEST_FLASK=3bcd417e5b93590944ebdba05ff4ae37aab31aadcda2e4514d8be275d52877191ff
# Cryptography
DIGEST_PYCPARSER=06dc9cefdcde6b97c96d0452a77db42a629c48ee545edd7ab241763e50e3b3c56d21f9fcce4e206817aa1a597763d948a10ccc73572490d739c89eea7fede0a1
DIGEST_CFFI=5b315a65fc8f40622ceef35466546620aaca9dd304f5491a845239659b4066469c5fb3f1683c382eb57f8975caf318e5d88852e3dbb049cde193c9189b88c9c0
DIGEST_CRYPTOGRAPHY=251d1ce022ac969516e54eae62b383bc113cc023a5459a030fa4c3d3d67c5ff4daa5d23bcf6a334845315ab71532e7aa3db28c882bbfed5260dd1ab01429ca6a
DIGEST_CFFI=d906b00752bce5634b309574a3edd5fbb737cb90df351cedcb7c264f3a5b93e28bdd16af1ff69eeddce745b1ada93d97c0b7fb73e04e6973fd7631850cba5f87
DIGEST_CRYPTOGRAPHY=3f208fbc6954a3c6e8dc1d1c20ff2c32f4154eac5cbbc4b0c96032cc33be73bfd99081eaba9eb1557b3ffa3dfcb5312f77fff1393bfb50ce2e8df7a8c585f128
# PyNaCl
DIGEST_SETUPTOOLS=c86448d2348b4f58e3eb4c55f8133675f3a20315ee11e829a55f414c07c05f84afe4991d95625a8f0ed62e924b34bff29fd8e67a6929298ec53f69e6fcc4454b
DIGEST_SETUPTOOLS=258cdd8b4bd49dcddd0097c2baf93be93dbffb1634ca1a984b5b71f84a0b37a8342c5725a06615bc8f0090c7193f24740db2f5c15a5b9b00f452fe0b14640c1c
DIGEST_PYNACL=bf1bb46d23419cb375bcf620a37b5e9ce925cb0dd55eadf851a4bbb9039c8846ed13ae33966436a96655ea41ad1fc282f9139a958fd55ea10597fd3859635a2f
@ -103,8 +103,8 @@ DIGEST_PYNACL=bf1bb46d23419cb375bcf620a37b5e9ce925cb0dd55eadf851a4bbb9039c8846ed
function verify_tcb_requirements_files {
# To minimize the time TCB installer configuration stays online,
# only the requirements files are authenticated between downloads.
compare_digest 01b139e85415cd60125eef077dd7bcac68952f605780ae91d3938dc2c8a80d7b7f06f2066c0d0aed1229ef2c69679f984919313dc4749db434b507902531e061 '' requirements.txt
compare_digest 76365ec0eb29cff5afa13079fefd4f69e903138a39a5bc3b6f717b5f33bfee0913bbe784678591c26a6962b0c2768764bf220c296e410259a67154a3ce8da031 '' requirements-venv.txt
compare_digest c550ef477416a55e9a73b2661690c4c898038b95cb431dae0682ae79eef8ad5fc6e57bcf2431d388416521b5b7a06ca4c62d95c016868501b97366ef95d5b5a3 '' requirements.txt
compare_digest 9698879e51dcf1f20222fe17aa16d6c0bfad35ed434700d573f6de6af62b9572692751640caba8ecd7b2974ad270017312efcc2524af9ec6224e6c4662ee08ff '' requirements-venv.txt
}
@ -114,48 +114,51 @@ function verify_files {
compare_digest d361e5e8201481c6346ee6a886592c51265112be550d5224f1a7a6e116255c2f1ab8788df579d9b8372ed7bfd19bac4b6e70e00b472642966ab5b319b99a2686 '' LICENSE
compare_digest 8db25eafc66308f1fe8223c39bc5fb025ae111ebce3eae5601c907fa7a2654f68395af4f355ff0ff03775e79cda8dfccddaf7d68555bfe065d9469ca04a288f9 '' LICENSE-3RD-PARTY
compare_digest 7cad2202e4cc940627e31577162c38f44022ddb138a51f52d0ac3747e264e065919df2b646020851d8973cc76a2873a72ceabcbe93c39911ebbfa7c867f01675 '' relay.py
compare_digest 48d0b6fac48973f2d6a9320b06c1f4cfce017533780ee74033ecbd49313f931ec8b50c385485c4e449394672188ff61ddc0229f8b08823637ad41a46ebcd58e8 '' requirements-dev.txt
compare_digest f4c434bbe18373eecc04133352194fbbc49c8d6d7d7a8bec605060421cce06e012a0d25fa0a6a6cfd91d9a30b2cc9369d8d0539209d551505982c8173eba8b54 '' requirements-relay.txt
compare_digest 5198d01cbee066ec0a1f0caacd73662ed8e911f8e65da0d7ab0e3ef3ca4563a70e4b0cb73c6d161947c11991bb02f268d9af56c9b55115e16d09c9d4ed6a6ea0 '' requirements-relay-tails.txt
compare_digest 20868b92578a7063851f98dbc3dacf0f9c7b317cb2a8f1497594092a9328a996666535ff1698021d8f9de2055c39a8019f565c49453b8400ce8db98897d280c9 '' requirements-setuptools.txt
compare_digest 3d504be8d4d516313033ebc27e002f58256bc03c6529b37047755e4d2aaf4946e93eab7b9a980cdb5ba742be2db3fbffc543e54b3d9f4bdefedf6ad4666b15aa '' requirements-dev.txt
compare_digest da93ae193d1ea3a248630fa253fccfcda4b7a4549c6e42e5ebef1f9e2bd902da8329b2f1e779cc80740cbb013047d432f84a34be5e0efec7b615621b33db5e61 '' requirements-relay.txt
compare_digest 305ef52145c91544e55279d264265dc542aba6ac73e130b3dabe7f43f3518799aae06de10e4e7a1a4c2e80a6cc859f3452ad1e2289f480ef7bebb50a325f988a '' requirements-relay-tails.txt
compare_digest 536db4ebff8904cb7403acefae29ce7f681bbfe5d765d0ef61c47b2654102302e16db04b826483e253c3c06aa094bde6e5ffe46180242f3079e19604532b0bd5 '' requirements-setuptools.txt
compare_digest 79f8272a2ab122a48c60630c965cd9d000dcafabf5ee9d69b1c33c58ec321feb17e4654dbbbf783cc8868ccdfe2777d60c6c3fc9ef16f8264d9fcf43724e83c2 '' tfc.png
compare_digest c746fa981fcdc1b21cbe7117ed186ef7757d120cb96fbe8500b8b5f7f4effebe71360ae5c1cc2bf873818002544d9aeba26990b93723a79c6bbcd647552a7ca0 '' tfc.py
compare_digest 62f26d2805570ee70fad3a076579a554008e7d9f2c9ff310f3bb5876d361cc03dbae7ab63b144ac215a35f920ac56d359481352805a356479d622ab00da15f7f '' tfc.yml
compare_digest c4d95b0385f474eee4ef8c25c579d5303a14ecbd90258d5cbd9c4d32531cec45008fa5fa0593c1babaeaf446e20b5ff5fcc8c7cc0384790be93e56065dc5dce5 '' uninstall.sh
compare_digest dd2dc76c186e718cd6eb501985f3f8c639d303e44186c5f21ca8e8cf6a25b329c573ca489eda80877be51ccaa6b72b4e763060d0f0f27c0217e8c6b1016d75ca launchers/ terminator-config-local-test
compare_digest d977069071e05ab3e654ac3f627e43f84d95e705e83b20f87346ae903d9aa578ed7b853e9a5e77f2cd064add6db77327c68a3cab1dae4c702b3a3e0e6c29b230 launchers/ TFC-Local-test.desktop
compare_digest 64fca605fff0722677e74f376f18b17d4fe07096294de16042860e91cc055c2ca33037a61aa1a9b9dde33d803b63f595e8d9cc20a5c36e952c145dc9fbb6e528 launchers/ TFC-Local-test.desktop
compare_digest fc193b64793fec001365e4c055e0f7894d3993e1a7dbcfd6dc63a8a04a9bb1c28fc455173243f5f7c2385b86b63f122ac6c6fe2c720a9ee92834fa76dbcc1672 launchers/ tfc-qubes-receiver
compare_digest 4aeeffde5b6f7d27a44a3ab9b8470b59b357f7532dd3c21de78d8ded0415e0dd49cbb8eb10b5b658a8cf32dbc4afbf2663a9bf1dec0e0ee3c443f4fcb9a2990d launchers/ tfc-qubes-relay
compare_digest 4098d69e5632db7a465b03bda89dcd9817aadb903cfc0a1deb76739fbb627fc6abffd092c922c9ee8f6c3368a142c56583d25787219cfe11e37467193e49fa85 launchers/ tfc-qubes-transmitter
compare_digest f46cee0d9c768514ab667d6e9d37838c05664bc2d2e27ab8d309827448465365ad53232f318ba24b70bd25c9bb535dfe5267b756c65e9f4152c534b8ad5d9e9d launchers/ TFC-RP.desktop
compare_digest 23c819b913a15cde5b6e15d2a93c399f00db0f927e0e3dc49232e8fe9e845c89f1660cf1d9cdb3c6f536071fbd546480bc7df314a95439c0ee3991a70469bc4e launchers/ TFC-RP-Qubes.desktop
compare_digest f46cee0d9c768514ab667d6e9d37838c05664bc2d2e27ab8d309827448465365ad53232f318ba24b70bd25c9bb535dfe5267b756c65e9f4152c534b8ad5d9e9d launchers/ TFC-RP-Tails.desktop
compare_digest 3a41c64aa8a7d8a3237b87667141f9fd85482b97caf606ea53b37b5f1dd775471eeb6b09e1292dc39018b77a3900a11c02c20d7ec983b8c6aadd3172afe35246 launchers/ TFC-RxP.desktop
compare_digest de983c81aeb56701763aec749a1500f3687706fd86be4e7f8c884359a20810ee1aa99fe28beea387527412468aa048375529d88d0c0a9951094daba3931535d1 launchers/ TFC-RxP-Qubes.desktop
compare_digest e51703df0106368858832936ef7f5048221ca2b1eb2bc3a41c06e1bfb055f5f9f00a712b1195511f3e7fbbf6c04b22752b3ad54d8ef68c75ebdbd145e310b98a launchers/ TFC-TxP.desktop
compare_digest 7dc114d5c6cc94b427a8c99c1bc10763ff5f42f2351d6a4d5058697a4d4d904106fc8f8382ad6d8632289fc9ae2d196aa8a1cc3514cb88dfd12e32ab3501a400 launchers/ TFC-TxP-Qubes.desktop
compare_digest 7a2673087fa278749dcc06310a59e9159e101c0ac4faa4003587071b7df732f5b6cde001df71ee01082f099018d3d1acfdb89300361f72852646dc1ea7081fc0 launchers/ TFC-RP.desktop
compare_digest 6ac845238824f0ede5b6db6403e9096f0f16050195f179b759cb21249259491363f37b178301ffb50ad1a4919a32a44318c43eae48ac12404de72e33aa672a85 launchers/ TFC-RP-Qubes.desktop
compare_digest 7a2673087fa278749dcc06310a59e9159e101c0ac4faa4003587071b7df732f5b6cde001df71ee01082f099018d3d1acfdb89300361f72852646dc1ea7081fc0 launchers/ TFC-RP-Tails.desktop
compare_digest 34ee06aee9b471d7823c8c05c6ccf7a99716231aaaee11e1993281e7bfce2617c378594a402768247c482390f26241829ed99d2134912a94b9fae880448951b7 launchers/ TFC-RxP.desktop
compare_digest b84115bcbc76753e1702da3eccf81a3fca65cb09d085904c2ec660b9d1a539409d181603ab08c1fef2716d3bb41c686602d9bbe6611f17725eeb6fa52422ac61 launchers/ TFC-RxP-Qubes.desktop
compare_digest 6661c1181b0eda5773425849bd58d0118936f9285db295ad998b98c96c8c72e3a8e897e70eea4d06d288bf33bc66548d8774ed5afcb75e4eee7d64b40303b12f launchers/ TFC-TxP.desktop
compare_digest 823ec5061fb7d4a4234ce30c0054c8006d63a1ef719f8c41fad8855c8d096fba59139601f73a9e878a18c5f3377b8de1e80a69e735137bb42f7bae9c4360d6af launchers/ TFC-TxP-Qubes.desktop
compare_digest b5393d094dce97a8e12a36438c56e92da8dc01555aa18a85b138411d18a3e47384a2dc56b184f8d18745db55495d6f2e22e4aa0038a5b2917856a1ba13fca06a qubes/ service.sh
compare_digest 070fb411f13dac93a8e11be5a97857f00d05e312f152736f2e9757397bc045cc235212121458c773daf1251e512e37836525dcda394dabc78879e71159f79850 qubes/ writer.py
compare_digest 3ee90ee305382d80da801f047a6e58e5b763f9f6bc08dce531d5c620f2748c6bba59a1528eee5d721decb8e724f53b28fc7609f5b20472f679f554b78b5d4cc6 src/ __init__.py
compare_digest 3ee90ee305382d80da801f047a6e58e5b763f9f6bc08dce531d5c620f2748c6bba59a1528eee5d721decb8e724f53b28fc7609f5b20472f679f554b78b5d4cc6 src/common/ __init__.py
compare_digest f6572b3e2b446405a4af1a1a197787d40bf980f80c19569b33ff503f0b3a312a1e78076ee19095ad149930d7919b9fb468d3937eef44012fd9a926a8bf0658c7 src/common/ crypto.py
compare_digest b87ad9321dedc59fd17d1a60866ed061925870156a458861d5c51d5825f8c5562c9a33d8f8d14a46c6b054a6542c8aa5d97c06ce78442f66913e8ab043fa20de src/common/ database.py
compare_digest 654cc2aa00b91697c3dc1894c4f7726d151e2c2fc92922aa760008c6f55645129a546e0fa38c39e2c72d10720b7a8d67253bfcddddc01dde4a99fdc40143ffad src/common/ crypto.py
compare_digest e1f77d17164d6879695adb0ad9d32c61f7447f36db73e398e161956140a142c40a9972970d89873d2febd4dc1c2b57ddd838eccd3437fa28c83285a0179aeb45 src/common/ database.py
compare_digest dfef16b30d75bbe270c4b7df1369b3eeb2347b931e7bb3a974965cc916a6ffb20aaa40d14532ecb4a8cabdb71598fb53d86589aa475dbb02030bdf9489d71429 src/common/ db_contacts.py
compare_digest 7c0214208857174b43092eaf61d14c16e60d6ebb68ba25b260f84546ce39f1fed8b21aceb58833920c8d939304b313c0ad95c554210ae3d5d0547143f7dd704c src/common/ db_groups.py
compare_digest c49231429824d8133de7efad667c2bdde694a6c7a2e34e3b015ddb8cf59a150574cdd7099aaad02a4993a1669cd631f5af4cc611fac7d538d3ecd141d9295d0d src/common/ db_keys.py
compare_digest 04e0c0d53bcfc71476410bbdfcacee2ba3df6d7761d02111aca69a56cac848e4fb0178ee572b181b1a925bd45aae005b31b9e2afcce7416f7bd8c5dad96bc615 src/common/ db_logs.py
compare_digest 59affc49355ee6c3be47fde56434142ec87ce49554194744b67fa7f7f9e24e9008fe23ad5ee196b74adc49a1daf555d42762630e44be5b55294f7bd223b77883 src/common/ db_logs.py
compare_digest 82286a267814ba58fee37477a44ecd87090ce4878535bd98f626ef9b853965f6f18d082109713856ef19c80ec892fc26ad4a5c08775a0a0ca65134a9d3ed86d5 src/common/ db_masterkey.py
compare_digest 325298cd6cb7e68d27681c18f29e635f46222e34015ba3c8fe55e6718e6907b4257bbe12d71fd344b557aff302ae9d7fca2b581b4208e59ac7923e57aca23fe5 src/common/ db_onion.py
compare_digest 4ef757ba877ee6b74632af3a0d3567c9483a62b9063ec0e7fe7b6abc7e82b490ec52279198f0be22866595dae1948bb1ef9ef556c88b3c320c5316fd59fc0743 src/common/ db_settings.py
compare_digest 60fb4c922af286307865b29f0cadab53a5a575a9f820cd5ad99ea116c841b54dd1d1be1352bf7c3ab51d2fd223077217bcda1b442d44d2b9f1bf614e15c4a14d src/common/ encoding.py
compare_digest ccd522408ad2e8e21f01038f5f49b9d82d5288717f1a1acf6cda278c421c05472827ee5928fbf56121c2dfc4f2cc49986e32c493e892bd6ae584be38ba381edd src/common/ exceptions.py
compare_digest 6a0b92cc259f7f0b4d1b65663ea633cc49590ff3562e1fedb096b59b49eddcbffa5e1892a6a5873a879f13b666192d3986f2c010de2e994ae7f6f6119b49ab60 src/common/ gateway.py
compare_digest 5a146b3cfee9e703453a25bbbf347a87c3422ccefcd9083697d9ebdcf476b7be725518060fa09ea7ff98757b2b3c0ee687cb7b5c24a9938d6d9f17a7fc00505c src/common/ gateway.py
compare_digest d4021175fba75649fa1b8b65116b0acc98cedccd2a012986037a78e799908329694ee6f4c50617f92f5df279cfe5e719e38cada5f3775a8ea912a541f1dbf438 src/common/ input.py
compare_digest 8045671a2d180271ea873e91e478a0b3ba766cda195a0755060ba14fb50d089b7007b6134c002e8d25255e47376c2e394c76a7593e68ea45f1cc1f8e109869e9 src/common/ misc.py
compare_digest 6329bbdc9d24c1342d0996009a8cd4d852d5a800cbf6a582c047c0fc13e6ca9be28251b783325adffca100d2a372616088cedff2441cc103b8c18540828445ef src/common/ output.py
compare_digest c96d7cb1b76650a49accc3ea007254e73e2e697895790ff6c14351520f4a7b1baec76d6055e3bddb14a687c0641fd15e361c93737afe7a8924b420ca67c31140 src/common/ path.py
compare_digest 39e48b0b55f4f1a48bc558f47b5f7c872583f3f3925fd829de28710024b000fcb03799cb36da3a31806143bc3cbb98e5d357a8d62674c23e1e8bf957aece79f6 src/common/ reed_solomon.py
compare_digest a047c5e4dde0c5a85917fdaa76800913de90eed983685599c8b1c6376114111e0cd4eaa8dc89069f85f7d01c2926d8a856cc1edc8b672b7c22ad9c19528522cb src/common/ statics.py
compare_digest 04127f6615143806cee59140b325021fcccc3852d0f2eb6fda84f55268ddc5f4b22af2120a44b3d17e88024807236c97e1a2b60d7dd2c5abf91c521f32b68796 src/common/ statics.py
compare_digest a57d5525a570a78d15c75e79702289cf8571c1b3c142fae57f32bf3ed8bb784c7f63ce2e805d295b4a505fdeaf9d59094ebe67d8979c92dc11e2534474505b0e src/common/ word_list.py
compare_digest 3ee90ee305382d80da801f047a6e58e5b763f9f6bc08dce531d5c620f2748c6bba59a1528eee5d721decb8e724f53b28fc7609f5b20472f679f554b78b5d4cc6 src/receiver/ __init__.py
@ -185,7 +188,7 @@ function verify_files {
compare_digest 110665f962eb827a9f636cc823837222a7bed4a429d4e10eb90c7bf5ba7bd5900aa1ecc4d4b485927a276d5727e18fe9e78f75ab8bd4ff67f039bb633fe505ec src/transmitter/ input_loop.py
compare_digest 89407e887d0cba4d993c0ee60412ea1ecfdedd8bbb0c73417bb71847733f85dbe1dab2997f65824ae58b4b5278bb0866a2a04bb8273228ca1bbbc1068eec7c04 src/transmitter/ key_exchanges.py
compare_digest 766b1efa548f2da49272870fa5f89b8aacdf65b737b908f7064209f2f256c4d4875228ad087ac4957a292a82ed5936a40b9ae7553bfae2eae739f0c4579eb21a src/transmitter/ packet.py
compare_digest b8cfc11ae235c8cddbbd4003f8f95504456d9b2d6b6cc09bd538c09132bc737b6f070bdbc8d697e9ddfc5854546575526fa26c813f9f6bff7dc32fcdbb337753 src/transmitter/ sender_loop.py
compare_digest 0ed708842241e8b3a76371ea2857f8d6f035424a6a22af097a288d941c37e5d7142d2fa47e4e12cedbf0a06dab94222ff37a00292762660acb0d51f911acfb7a src/transmitter/ sender_loop.py
compare_digest c102bb337ade562e0d9aedc0910f70f14652e2eba004a632bfb0ba8dddf147ab271d3ae544c4d9f3b2fcd3830646d9ad28255717d017cb91b3463829069360ba src/transmitter/ traffic_masking.py
compare_digest eb77c6206cab63ffdb47bbcb8b76a55100636d893e234a048221d83e9ce07b76ccfcc93b506d9fb48d6f8823135e5697f3e56aed8e95f23990d8dfc1cece325e src/transmitter/ user_input.py
compare_digest 489f869176da0040b6f06327544f5eb72863a748a4799c66198a09402df6d54d842e9af27af51faaeed9d0661133eeaebb9918bd1bcd50950c182ba4b1e5fc74 src/transmitter/ window_mock.py
@ -425,6 +428,7 @@ function install_tcb {
process_virtualenv_dependencies "rm"
process_tcb_dependencies "rm -f"
sudo rm -r "${INSTALL_DIR}/src/relay/"
sudo rm -r "${INSTALL_DIR}/qubes/"
sudo rm "${INSTALL_DIR}/dd.py"
sudo rm "${INSTALL_DIR}/relay.py"
sudo rm "${INSTALL_DIR}/tfc.yml"
@ -458,6 +462,7 @@ function install_relay {
process_tcb_dependencies "rm -f"
sudo rm -r "${INSTALL_DIR}/src/receiver/"
sudo rm -r "${INSTALL_DIR}/src/transmitter/"
sudo rm -r "${INSTALL_DIR}/qubes/"
sudo rm "${INSTALL_DIR}/dd.py"
sudo rm "${INSTALL_DIR}/tfc.py"
sudo rm "${INSTALL_DIR}/tfc.yml"
@ -513,6 +518,7 @@ function install_relay_tails {
process_tails_dependencies "rm"
t_sudo rm -r "${INSTALL_DIR}/src/receiver/"
t_sudo rm -r "${INSTALL_DIR}/src/transmitter/"
t_sudo rm -r "${INSTALL_DIR}/qubes/"
t_sudo rm "${INSTALL_DIR}/dd.py"
t_sudo rm "${INSTALL_DIR}/tfc.py"
@ -527,7 +533,6 @@ function install_qubes_src {
create_user_data_dir
steps_before_network_kill
qubes_src_firewall_config
verify_files
@ -547,6 +552,7 @@ function install_qubes_src {
process_virtualenv_dependencies "rm"
process_tcb_dependencies "rm -f"
sudo rm -r "${INSTALL_DIR}/src/relay/"
sudo rm -r "${INSTALL_DIR}/qubes/" # Listening service only needed on NET/DST
sudo rm "${INSTALL_DIR}/dd.py"
sudo rm "${INSTALL_DIR}/relay.py"
sudo rm "${INSTALL_DIR}/tfc.yml"
@ -560,10 +566,14 @@ function install_qubes_dst {
create_user_data_dir
steps_before_network_kill
qubes_dst_firewall_config
verify_files
# Configure listening service for qrexec RPC
sudo ln -sf /opt/tfc/qubes/service.sh /etc/qubes-rpc/tfc.NetworkerDestination
sudo chmod a+x /opt/tfc/qubes/writer.py
sudo chmod a+x /opt/tfc/qubes/service.sh
process_virtualenv_dependencies "python3 -m pip install"
sudo python3 -m virtualenv "${INSTALL_DIR}/venv_tcb" --system-site-packages --never-download
@ -593,10 +603,14 @@ function install_qubes_net {
create_user_data_dir
steps_before_network_kill
qubes_net_firewall_config
verify_files
# Configure listening service for qrexec RPC
sudo ln -sf /opt/tfc/qubes/service.sh /etc/qubes-rpc/tfc.SourceNetworker
sudo chmod a+x /opt/tfc/qubes/writer.py
sudo chmod a+x /opt/tfc/qubes/service.sh
process_virtualenv_dependencies "python3 -m pip install"
sudo python3 -m virtualenv ${INSTALL_DIR}/venv_relay --system-site-packages
@ -611,6 +625,7 @@ function install_qubes_net {
# Remove unnecessary files
remove_common_files "sudo"
process_virtualenv_dependencies "rm"
process_tcb_dependencies "rm -f"
sudo rm -r "${INSTALL_DIR}/src/receiver/"
sudo rm -r "${INSTALL_DIR}/src/transmitter/"
sudo rm "${INSTALL_DIR}/dd.py"
@ -620,92 +635,6 @@ function install_qubes_net {
install_complete_qubes
}
# Qubes firewall configurations
function add_fw_rule {
# Add a firewall rule that takes effect immediately
sudo ${1}
# Make the firewall rule persistent
echo "${1}" | sudo tee -a /rw/config/rc.local
}
function qubes_src_firewall_config {
# Edit Source VM's firewall rules to block all incoming connections,
# and to only allow UDP packets to Networked VM's TFC port.
# Create backup of the current rc.local file (firewall rules)
sudo mv /rw/config/rc.local{,.backup."$(date +%Y-%m-%d-%H_%M_%S)"}
# Add firewall rules that block all incoming/outgoing connections
add_fw_rule "iptables --flush"
add_fw_rule "iptables -t filter -P INPUT DROP"
add_fw_rule "iptables -t filter -P OUTPUT DROP"
add_fw_rule "iptables -t filter -P FORWARD DROP"
src_ip=$(sudo ifconfig eth0 | grep "inet" | cut -d: -f2 | awk '{print $2}')
net_ip=$(get_net_ip)
# Allow export of data to the Networked VM
add_fw_rule "iptables -I OUTPUT -s ${src_ip} -d ${net_ip} -p udp --dport 2063 -j ACCEPT"
sudo chmod a+x /rw/config/rc.local
# Store Networked VM IP address so Transmitter Program can configure itself
echo ${net_ip} > $HOME/tfc/rx_ip_addr
}
function qubes_dst_firewall_config {
# Edit Destination VM's firewall rules to block all outgoing connections,
# and to only allow UDP packets from Networked VM to Receiver Programs' port.
# Create backup of the current rc.local file (firewall rules)
sudo mv /rw/config/rc.local{,.backup."$(date +%Y-%m-%d-%H_%M_%S)"}
# Add firewall rules that block all connections
add_fw_rule "iptables --flush"
add_fw_rule "iptables -t filter -P INPUT DROP"
add_fw_rule "iptables -t filter -P OUTPUT DROP"
add_fw_rule "iptables -t filter -P FORWARD DROP"
net_ip=$(get_net_ip)
dst_ip=$(sudo ifconfig eth0 | grep "inet" | cut -d: -f2 | awk '{print $2}')
# Allow import of data from the Networked VM
add_fw_rule "iptables -I INPUT -s ${net_ip} -d ${dst_ip} -p udp --dport 2064 -j ACCEPT"
sudo chmod a+x /rw/config/rc.local
}
function qubes_net_firewall_config {
# Edit Networked VM's firewall rules to accept UDP
# packets from Source VM to the Relay Program's port.
net_ip=$(sudo ifconfig eth0 | grep "inet" | cut -d: -f2 | awk '{print $2}')
tcb_ips=$(get_tcb_ips)
src_ip=$(echo ${tcb_ips} | awk -F "|" '{print $1}')
dst_ip=$(echo ${tcb_ips} | awk -F "|" '{print $2}')
# Store Destination VM IP address so Relay Program can configure itself
echo ${dst_ip} > $HOME/tfc/rx_ip_addr
# Create backup of the current rc.local file (firewall rules)
sudo cp /rw/config/rc.local{,.backup."$(date +%Y-%m-%d-%H_%M_%S)"}
# Add firewall rules
add_fw_rule "iptables -t filter -P INPUT DROP"
add_fw_rule "iptables -t filter -P OUTPUT ACCEPT"
add_fw_rule "iptables -t filter -P FORWARD DROP"
add_fw_rule "iptables -I INPUT -s ${src_ip} -d ${net_ip} -p udp --dport 2063 -j ACCEPT" # 5. Whitelist UDP packets from SRC VM to NET VM's TFC port (2063)
add_fw_rule "iptables -I OUTPUT -d ${dst_ip} -p udp ! --dport 2064 -j DROP" # 4. Blacklist all UDP packets from NET VM to DST VM that don't have destination port 2064
add_fw_rule "iptables -I OUTPUT -d ${dst_ip} ! -p udp -j DROP" # 3. Blacklist all non-UDP packets from NET VM to DST VM
add_fw_rule "iptables -I OUTPUT ! -s ${net_ip} -d ${dst_ip} -j DROP" # 2. Blacklist all packets to DST VM that do not originate from NET VM
add_fw_rule "iptables -I OUTPUT -d ${src_ip} -p all -j DROP" # 1. Blacklist all packets to SRC VM
sudo chmod a+x /rw/config/rc.local
}
# Tiling terminal emulator configurations for single OS
function install_local_test {
@ -734,7 +663,8 @@ function install_local_test {
remove_common_files "sudo"
process_virtualenv_dependencies "rm"
process_tcb_dependencies "rm -f"
sudo rm "${INSTALL_DIR}/tfc.yml"
sudo rm -r "${INSTALL_DIR}/qubes/"
sudo rm "${INSTALL_DIR}/tfc.yml"
install_complete "Installation of TFC for local testing is now complete."
}
@ -802,57 +732,6 @@ function compare_digest {
}
function valid_ip() {
# Validate an IP-address. (Borrowed from https://www.linuxjournal.com/content/validating-ip-address-bash-script)
local ip=$1
local valid=1
if [[ ${ip} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=(${ip})
IFS=${OIFS}
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
valid=$?
fi
return ${valid}
}
function get_net_ip {
# Get the IP-address of the Networker VM from the user.
ip=$(zenity --entry --title="TFC Installer" --text="Enter the IP-address of the Networked Computer VM:")
if valid_ip ${ip}; then
echo ${ip}
return
else
zenity --info --title='TFC installer' --text='Error: Invalid IP'
get_net_ip
fi
}
function get_tcb_ips {
# Get the Source and Destination VM IP-addresses from the user.
ips=$(zenity --forms \
--title="TFC Installer" \
--text="Enter the IP-addresses of the TCB VMs" \
--add-entry="Source Computer VM IP:" \
--add-entry="Destination Computer VM IP:")
first_ip=$(echo ${ips} | awk -F "|" '{print $1}')
second_ip=$(echo ${ips} | awk -F "|" '{print $2}')
if valid_ip ${first_ip} && valid_ip ${second_ip}; then
echo ${ips}
return
else
zenity --info --title='TFC installer' --text='Error: Invalid IP'
get_tcb_ips
fi
}
function t_sudo {
# Execute command as root on Tails.
echo "${sudo_pwd}" | sudo -S $@
@ -1013,9 +892,9 @@ function arg_error {
clear
echo -e "\nUsage: bash install.sh [OPTION]\n"
echo "Mandatory arguments"
echo " tcb Install Transmitter/Receiver Program (Debian 10 / PureOS 9.0+ / *buntu 20.04+ / LMDE 4)"
echo " relay Install Relay Program (Debian 10 / PureOS 9.0+ / *buntu 20.04+ / LMDE 4 / Tails 4.0+)"
echo -e " local Install insecure local testing mode (Debian 10 / PureOS 9.0+ / *buntu 20.04+ / LMDE 4)\n"
echo " tcb Install Transmitter/Receiver Program (Debian 10 / PureOS 9.0+ / *buntu 20.04+ / LMDE 4 / Mint 20)"
echo " relay Install Relay Program (Debian 10 / PureOS 9.0+ / *buntu 20.04+ / LMDE 4 / Mint 20 / Tails 4.0+)"
echo -e " local Install insecure local testing mode (Debian 10 / PureOS 9.0+ / *buntu 20.04+ / LMDE 4 / Mint 20)\n"
echo " qsrc Install Transmitter Program (Qubes 4.0.3)"
echo " qdst Install Receiver Program (Qubes 4.0.3)"
echo -e " qnet Install Relay Program (Qubes 4.0.3)\n"
@ -1101,10 +980,10 @@ function python_version_check {
# Check Python version and select file names based on it.
python_minor_version=$(python3 -c 'import sys; version=sys.version_info[:3]; print("{1}".format(*version))')
if (( ${python_minor_version} == 7 )); then
if (( python_minor_version == 7 )); then
CFFI=${CFFI37}
CRYPTOGRAPHY=${CRYPTOGRAPHY37}
elif (( ${python_minor_version} == 8 )); then
elif (( python_minor_version == 8 )); then
CFFI=${CFFI38}
CRYPTOGRAPHY=${CRYPTOGRAPHY38}
else

View File

@ -1,16 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEl5FK2MnA6CCzoiBaU4CsIVNsvGAFAl77+MwACgkQU4CsIVNs
vGDanw//cyxiBNoYlDe//bjYObZz1PGzPCBtqos/4ZqOHg4J+o8APYAuGWT9QQek
kKbvN1S68p0sHbOnmRytT52vNFmDmlEFfa1Cr0qhOvnyrGHHUNjX28CgHNgqxHo8
Y4DGe8f2pOsJBO5Hn2uP9t0UCfiD7/tkULl4ps71JcDrp6hNwdQmC1aINow9GVjW
9WxG9TNksqB5qN/Izrlzen5nErm1YlRINxlevL88yqSljAfD3ak01FewKMdsBR19
i4bjH6BLVCieS2hgG2hNxvcUeethLv2YJ2hFVtmuxRJeEuz2YD1NAD4DZA9hkm66
C325rROonfQUUdHs+F+3n5SjyyvqSL3LJeNQIyU7gp61IafEnhsJlKNR/m7CalHx
ptscyOLgHwFoFyGcR1M2HbGtoCmLVjujlLc7aYwYQVCQt9hB0syLd9dT8gmwIJ+K
fcIYBw+FCpBg5sIY0uCCKFsS7DyL39hwlOgNFF1nh2mSHsrhd3HbAztUEaUOLveL
vstbMHtetmVxidNyXpS0KmwjM/XwfgPcc2hqt9WkfIip3FiLv99tstv+9NGM84hI
vH6c8jsiliGOaKwWIrtDKwdKPOS6cwkghrXyz18lk+62BKWjOzcaTUQ4QjuILkt0
MMgaK+9klKSUPeCV68wuH+qjWmB+dMbBmFH+cyiX5jxkQYAP8lg=
=Je+N
iQIzBAABCAAdFiEEl5FK2MnA6CCzoiBaU4CsIVNsvGAFAl9/rcEACgkQU4CsIVNs
vGD23w//WLZ58BfKRR0v7KrCCbSrX+sryQPZ0Ky51/bFHWI/eu6t3N9i+UhVAMdX
WDNteKUtHtJNDd9QSpDK+jW0jAYdoNY9mXHO8fduSZiCkR8swEIgDsUc07FOV7L3
9aGxyvuUmsT8p8M1+p6afsIxlnh4WeYtsu6FduWBoAfcVMO8S/ZrbxJN8+GhFKDu
zvwNsMpeeEgTOlzcqRfg4pVfdNIkJGvUA2UQpEJiQJ52Hk2bDWKhyc8cmPqXIfjY
h4OffozvtmhGwf8jWY59qJ4ZZSCQN8pZMo04i2teL6CX7cE7R0amyo0pv7HkqIUh
IyOVNh4bMEdEim7iFMJeU2CE+IMA/WBRU0BsuCBzhKt7jUXf73F2V5CdOt/xou3Q
3su/PdNcANKiH6fEQwDgYBWGaMRBedOmSZlgq4PEWh768RN+25w+QnyHuFyrskeO
ezzUyPKGRip3wk8vb2P7cx/vlocCACcoiFtzr89Esp4f3qaDgyqy5erFGiSeG/Y9
pkOeibqyGAQ7HaIHEqCKwD2Z+gyj49PGb69CDTZZ+s9GU/mofHgMgogrZevidXZH
Vn2+EnBCIqT2YyAKl+f3Ap1FC1yOLUDUa4wRPwdqZ6/SVQVi2kzjuN8lTJfnZfzz
AzB2I+gVNJBacK/Nm82mepDJMTAWGPVAFE60PHNFl6x0PbMNRMU=
=vrgn
-----END PGP SIGNATURE-----

View File

@ -1,5 +1,5 @@
[Desktop Entry]
Version=1.20.07
Version=1.20.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.20.07
Version=1.20.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,5 +1,5 @@
[Desktop Entry]
Version=1.20.07
Version=1.20.10
Name=TFC-Relay
Exec=gnome-terminal --geometry=94x25 -x bash -c "source /opt/tfc/venv_relay/bin/activate && python3 /opt/tfc/relay.py -q && deactivate || bash"
Icon=tfc.png

View File

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

View File

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

View File

@ -1,5 +1,5 @@
[Desktop Entry]
Version=1.20.07
Version=1.20.10
Name=TFC-Receiver
Exec=gnome-terminal --geometry=94x25 -x bash -c "source /opt/tfc/venv_tcb/bin/activate && python3 /opt/tfc/tfc.py -r -q && deactivate || bash"
Icon=tfc.png

View File

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

View File

@ -1,5 +1,5 @@
[Desktop Entry]
Version=1.20.07
Version=1.20.10
Name=TFC-Transmitter
Exec=gnome-terminal --geometry=94x25 -x bash -c "source /opt/tfc/venv_tcb/bin/activate && python3 /opt/tfc/tfc.py -q && deactivate || bash"
Icon=tfc.png

View File

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

19
qubes/service.sh Normal file
View File

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

76
qubes/writer.py Normal file
View File

@ -0,0 +1,76 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TFC - Onion-routed, endpoint secure messaging system
Copyright (C) 2013-2020 Markus Ottela
This file is part of TFC.
TFC is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TFC. If not, see <https://www.gnu.org/licenses/>.
"""
import base64
import os
import sys
BUFFER_FILE_DIR = '/home/user/tfc/.buffered_incoming_packets'
BUFFER_FILE_NAME = 'buffered_incoming_packet'
def ensure_dir(directory: str) -> None:
"""Ensure directory exists."""
name = os.path.dirname(directory)
if not os.path.exists(name):
try:
os.makedirs(name)
except FileExistsError:
pass
def store_unique(file_data: bytes, # File data to store
file_dir: str, # Directory to store file
file_name: str # Preferred name for the file.
) -> None:
"""Store file under a unique filename.
If file exists, add trailing counter .# with value as large as
needed to ensure existing file is not overwritten.
"""
ensure_dir(f'{file_dir}/')
ctr = 0
while os.path.isfile(f"{file_dir}/{file_name}.{ctr}"):
ctr += 1
with open(f"{file_dir}/{file_name}.{ctr}", 'wb+') as f:
f.write(file_data)
f.flush()
os.fsync(f.fileno())
def main() -> None:
"""Store data from STDIN to unique file for Relay/Receiver Program.
To prevent adversaries from delivering malicious binaries on DestinationVM,
this utility encodes received raw bytes with Base85, that is decoded by the
Receiver Program prior to further authentication.
"""
data = sys.stdin.buffer.read()
store_unique(file_data=base64.b85encode(data),
file_dir=BUFFER_FILE_DIR,
file_name=BUFFER_FILE_NAME)
if __name__ == '__main__':
main()

30
requirements-dev.txt Normal file → Executable file
View File

@ -2,12 +2,12 @@
# Argon2 Password Hashing Function (Derives keys that protect persistent user data)
argon2-cffi>=20.1.0
cffi>=1.14.0
cffi>=1.14.3
pycparser>=2.20
six>=1.15.0
# cryptography (pyca) (Provides X448 key exchange)
cryptography>=2.9.2
cryptography>=3.1.1
# Flask (Onion Service web server that serves TFC public keys and ciphertexts to contacts)
Flask>=1.1.2
@ -21,13 +21,13 @@ Werkzeug>=1.0.1
mypy>=0.782
mypy-extensions>=0.4.3
typed-ast>=1.4.1
typing-extensions>=3.7.4.2
typing-extensions>=3.7.4.3
# PyLama (Code audit tool for Python)
pylama>=7.7.1
mccabe>=0.6.1
pycodestyle>=2.6.0
pydocstyle>=5.0.2
pydocstyle>=5.1.1
snowballstemmer>=2.0.0
pyflakes>=2.2.0
@ -41,11 +41,11 @@ pyserial>=3.4
PySocks>=1.7.1
# pytest (Test framework)
pytest>=5.4.3
attrs>=19.3.0
importlib-metadata>=1.7.0
zipp>=3.1.0
more-itertools>=8.4.0
pytest>=6.1.1
attrs>=20.2.0
importlib-metadata>=2.0.0
zipp>=3.3.0
more-itertools>=8.5.0
packaging>=20.4
pyparsing>=2.4.7
pluggy>=0.13.1
@ -53,22 +53,22 @@ py>=1.9.0
wcwidth>=0.2.5
# pytest-cov (Pytest plugin for measuring coverage)
pytest-cov>=2.10.0
coverage>=5.1
pytest-cov>=2.10.1
coverage>=5.3
# xdist (Pytest distributed testing plugin)
pytest-xdist>=1.32.0
pytest-xdist>=2.1.0
execnet>=1.7.1
apipkg>=1.5
pytest-forked>=1.2.0
pytest-forked>=1.3.0
# Requests (Connects to the contact's Tor Onion Service)
requests>=2.24.0
certifi>=2020.6.20
chardet>=3.0.4
idna>=2.10
urllib3>=1.25.9
setuptools>=47.3.1
urllib3>=1.25.10
setuptools>=50.3.0
# Stem (Connects to Tor and manages Onion Services)
stem>=1.8.0

12
requirements-relay-tails.txt Normal file → Executable file
View File

@ -11,7 +11,7 @@ requests==2.24.0 --hash=sha512:64c49592455abbcd1168f5e1908a8db77bbeb373264b1
certifi==2020.6.20 --hash=sha512:960f1cbe72443230ecba527b5bc4bb8a45a33feb646b0ad01dcb606b9ec3729d27dff5cfa04655d92efd4dec691d61c62d80f8fd39a82fc21528727eeb5c9991
chardet==3.0.4 --hash=sha512:bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4
idna==2.10 --hash=sha512:7b7be129e1a99288aa74a15971377cb17bee1618843c03c8f782e287d0f3ecf3b8f26e3ea736444eb358f1d6079131a7eb291446f3279874eb8e00b624d9471c
urllib3==1.25.9 --hash=sha512:b20687b4ce06164c5b932b43c5b758efd864668ee2b60f6cd6ce6c27f0ea16b9d1222ec0c061618fc3f0de362c0f18be95864bd91ecaa73fdfa92bd666fb4378
urllib3==1.25.10 --hash=sha512:b4e88397d5ac77c669c8bf01fdf6a70ec7cc3acbdf69e0113ce9a5cd8394e932e6e153d85c60978917e8bf6e85e15f1a4c7da10d2a10cfa0735884a5861a981d
# Flask (Onion Service web server that serves TFC public keys and ciphertexts to contacts)
Flask==1.1.2 --hash=sha512:3bcd417e5b93590944ebdba05ff4ae37aab31aadcda2e4514d8be275d52877191ffbc58d89ea603900afe39264c899fc1e4fd77cd5e24880c03601551d8f1aac
@ -23,13 +23,13 @@ MarkupSafe==1.1.1 --hash=sha512:69e9b9c9ac4fdf3cfa1a3de23d14964b843989128f8cc
Werkzeug==1.0.1 --hash=sha512:8f05b3632d00b1a9c3d85f46dccc7eb55c032bc8cc7b688219865487c96127ecccdd44f9724159299d14db98c1951b552b478811d292d93aa2d12817c88c8527
# cryptography (pyca) (Handles URL token derivation)
cryptography==2.9.2 --hash=sha512:251d1ce022ac969516e54eae62b383bc113cc023a5459a030fa4c3d3d67c5ff4daa5d23bcf6a334845315ab71532e7aa3db28c882bbfed5260dd1ab01429ca6a \
--hash=sha512:3fd9dbc776bb9ff015b96174aa424d4a33c60df7f2b07a35c16c080bfaca06fe35466b02a8586157979abe15c69354923c44c9ab238b850de8bdb1b45814f0bd
cffi==1.14.0 --hash=sha512:5b315a65fc8f40622ceef35466546620aaca9dd304f5491a845239659b4066469c5fb3f1683c382eb57f8975caf318e5d88852e3dbb049cde193c9189b88c9c0 \
--hash=sha512:af327aaee006c4d76034c6df9ffc2d7dd5d9fface5117007065db2bfef842ade3aae36727ff317c3e7f735facdfb5380e646bcd9615b14acfd2701a84c0d15ec
cryptography==3.1.1 --hash=sha512:3f208fbc6954a3c6e8dc1d1c20ff2c32f4154eac5cbbc4b0c96032cc33be73bfd99081eaba9eb1557b3ffa3dfcb5312f77fff1393bfb50ce2e8df7a8c585f128 \
--hash=sha512:b6ab18c7ec3dc666c602d40d2b7626c09c9f78aafab62f340ee8f63b54a12c952c0031bb1e3a31bf9182e139ae58655b2f20056511ae95123981ccacda5bf855
cffi==1.14.3 --hash=sha512:d906b00752bce5634b309574a3edd5fbb737cb90df351cedcb7c264f3a5b93e28bdd16af1ff69eeddce745b1ada93d97c0b7fb73e04e6973fd7631850cba5f87 \
--hash=sha512:4fac15b1ef79c668f2e9e4fe9b71ca181b3d1ec61ca5ccd4608843f8c841ab58b460d3cb01b242f61d996351ab0fb50d686adf488f89866f6cbf796a2d333ded
pycparser==2.20 --hash=sha512:06dc9cefdcde6b97c96d0452a77db42a629c48ee545edd7ab241763e50e3b3c56d21f9fcce4e206817aa1a597763d948a10ccc73572490d739c89eea7fede0a1
six==1.15.0 --hash=sha512:0416d59434623604de755601c919722c2b800042612a2a7b221ecd3ccf556aca3a78f0f926fd640032a3d74d153457628a89c25065dfcdbb96892d5bf7279904
# PyNaCl (pyca) (Derives TFC account from Onion Service private key)
PyNaCl==1.4.0 --hash=sha512:bf1bb46d23419cb375bcf620a37b5e9ce925cb0dd55eadf851a4bbb9039c8846ed13ae33966436a96655ea41ad1fc282f9139a958fd55ea10597fd3859635a2f
setuptools==47.3.1 --hash=sha512:c86448d2348b4f58e3eb4c55f8133675f3a20315ee11e829a55f414c07c05f84afe4991d95625a8f0ed62e924b34bff29fd8e67a6929298ec53f69e6fcc4454b
setuptools==50.3.0 --hash=sha512:258cdd8b4bd49dcddd0097c2baf93be93dbffb1634ca1a984b5b71f84a0b37a8342c5725a06615bc8f0090c7193f24740db2f5c15a5b9b00f452fe0b14640c1c

12
requirements-relay.txt Normal file → Executable file
View File

@ -14,7 +14,7 @@ requests==2.24.0 --hash=sha512:64c49592455abbcd1168f5e1908a8db77bbeb373264b1
certifi==2020.6.20 --hash=sha512:960f1cbe72443230ecba527b5bc4bb8a45a33feb646b0ad01dcb606b9ec3729d27dff5cfa04655d92efd4dec691d61c62d80f8fd39a82fc21528727eeb5c9991
chardet==3.0.4 --hash=sha512:bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4
idna==2.10 --hash=sha512:7b7be129e1a99288aa74a15971377cb17bee1618843c03c8f782e287d0f3ecf3b8f26e3ea736444eb358f1d6079131a7eb291446f3279874eb8e00b624d9471c
urllib3==1.25.9 --hash=sha512:b20687b4ce06164c5b932b43c5b758efd864668ee2b60f6cd6ce6c27f0ea16b9d1222ec0c061618fc3f0de362c0f18be95864bd91ecaa73fdfa92bd666fb4378
urllib3==1.25.10 --hash=sha512:b4e88397d5ac77c669c8bf01fdf6a70ec7cc3acbdf69e0113ce9a5cd8394e932e6e153d85c60978917e8bf6e85e15f1a4c7da10d2a10cfa0735884a5861a981d
# Flask (Onion Service web server that serves TFC public keys and ciphertexts to contacts)
Flask==1.1.2 --hash=sha512:3bcd417e5b93590944ebdba05ff4ae37aab31aadcda2e4514d8be275d52877191ffbc58d89ea603900afe39264c899fc1e4fd77cd5e24880c03601551d8f1aac
@ -26,13 +26,13 @@ MarkupSafe==1.1.1 --hash=sha512:69e9b9c9ac4fdf3cfa1a3de23d14964b843989128f8cc
Werkzeug==1.0.1 --hash=sha512:8f05b3632d00b1a9c3d85f46dccc7eb55c032bc8cc7b688219865487c96127ecccdd44f9724159299d14db98c1951b552b478811d292d93aa2d12817c88c8527
# cryptography (pyca) (Handles URL token derivation)
cryptography==2.9.2 --hash=sha512:251d1ce022ac969516e54eae62b383bc113cc023a5459a030fa4c3d3d67c5ff4daa5d23bcf6a334845315ab71532e7aa3db28c882bbfed5260dd1ab01429ca6a \
--hash=sha512:3fd9dbc776bb9ff015b96174aa424d4a33c60df7f2b07a35c16c080bfaca06fe35466b02a8586157979abe15c69354923c44c9ab238b850de8bdb1b45814f0bd
cffi==1.14.0 --hash=sha512:5b315a65fc8f40622ceef35466546620aaca9dd304f5491a845239659b4066469c5fb3f1683c382eb57f8975caf318e5d88852e3dbb049cde193c9189b88c9c0 \
--hash=sha512:af327aaee006c4d76034c6df9ffc2d7dd5d9fface5117007065db2bfef842ade3aae36727ff317c3e7f735facdfb5380e646bcd9615b14acfd2701a84c0d15ec
cryptography==3.1.1 --hash=sha512:3f208fbc6954a3c6e8dc1d1c20ff2c32f4154eac5cbbc4b0c96032cc33be73bfd99081eaba9eb1557b3ffa3dfcb5312f77fff1393bfb50ce2e8df7a8c585f128 \
--hash=sha512:b6ab18c7ec3dc666c602d40d2b7626c09c9f78aafab62f340ee8f63b54a12c952c0031bb1e3a31bf9182e139ae58655b2f20056511ae95123981ccacda5bf855
cffi==1.14.3 --hash=sha512:d906b00752bce5634b309574a3edd5fbb737cb90df351cedcb7c264f3a5b93e28bdd16af1ff69eeddce745b1ada93d97c0b7fb73e04e6973fd7631850cba5f87 \
--hash=sha512:4fac15b1ef79c668f2e9e4fe9b71ca181b3d1ec61ca5ccd4608843f8c841ab58b460d3cb01b242f61d996351ab0fb50d686adf488f89866f6cbf796a2d333ded
pycparser==2.20 --hash=sha512:06dc9cefdcde6b97c96d0452a77db42a629c48ee545edd7ab241763e50e3b3c56d21f9fcce4e206817aa1a597763d948a10ccc73572490d739c89eea7fede0a1
six==1.15.0 --hash=sha512:0416d59434623604de755601c919722c2b800042612a2a7b221ecd3ccf556aca3a78f0f926fd640032a3d74d153457628a89c25065dfcdbb96892d5bf7279904
# PyNaCl (pyca) (Derives TFC account from Onion Service private key)
PyNaCl==1.4.0 --hash=sha512:bf1bb46d23419cb375bcf620a37b5e9ce925cb0dd55eadf851a4bbb9039c8846ed13ae33966436a96655ea41ad1fc282f9139a958fd55ea10597fd3859635a2f
setuptools==47.3.1 --hash=sha512:c86448d2348b4f58e3eb4c55f8133675f3a20315ee11e829a55f414c07c05f84afe4991d95625a8f0ed62e924b34bff29fd8e67a6929298ec53f69e6fcc4454b
setuptools==50.3.0 --hash=sha512:258cdd8b4bd49dcddd0097c2baf93be93dbffb1634ca1a984b5b71f84a0b37a8342c5725a06615bc8f0090c7193f24740db2f5c15a5b9b00f452fe0b14640c1c

2
requirements-setuptools.txt Normal file → Executable file
View File

@ -1 +1 @@
setuptools==47.3.1 --hash=sha512:c86448d2348b4f58e3eb4c55f8133675f3a20315ee11e829a55f414c07c05f84afe4991d95625a8f0ed62e924b34bff29fd8e67a6929298ec53f69e6fcc4454b
setuptools==50.3.0 --hash=sha512:258cdd8b4bd49dcddd0097c2baf93be93dbffb1634ca1a984b5b71f84a0b37a8342c5725a06615bc8f0090c7193f24740db2f5c15a5b9b00f452fe0b14640c1c

6
requirements-venv.txt Normal file → Executable file
View File

@ -1,10 +1,10 @@
# Sub-dependencies are listed below dependencies
# Virtual environment (Used to create an isolated Python environment for TFC dependencies)
virtualenv==20.0.25 --hash=sha512:812cc4b096e4357936d94c0e4f768e943eaf3b5ce1edd5ca309fc4433a3bf03ee7385cdeaf1a277408d250ecf28eb0e1d871da0818cf764d65109be42007e94e
virtualenv==20.0.33 --hash=sha512:c15f4b8d4df895ba03ecc19631198584c97e74b3026b7e82207e62d932ea03bb4c5dd36349764c1418021aa6611072a44c9b3166bbf7040cfc5815efba03b2d1
appdirs==1.4.4 --hash=sha512:8e6c1ea544013ea2567cda2d8b8c7b441bc50ac689aa7f95de67e3795aa083e9592c687d74fdbb37f5a75e0beab398fe47df5bced14ee9c204cfe5ecc364ef44
distlib==0.3.1 --hash=sha512:ac65d35a5309ec22db5b1e9ab6c20014084feab11e86e81bee6d0bfcc65940dfdcaa2711ac1e98c1ef179b110a4ea03dbaf042b894d3051da9d339c534664e00
filelock==3.0.12 --hash=sha512:d13edd50779bca9842694e0da157ca1fdad9d28166771275049f41dea4b8d8466fc5604b610b6ad64552cdf4c1d3cada9977ca37c6b775c4cc92f333709e8ea3
importlib-metadata==1.7.0 --hash=sha512:7146604e980d7921af3fd89351edba9919e2ff93879676adda7b1c55804b2d4b8cc6fbbd4064b5d03b5bc89a6a968b446f438deeb117412e140a676f05a785f8
zipp==3.1.0 --hash=sha512:89170b91cfdc0ef4d85b5316b484c8d6e01985f19bb9f545b11d648e122392efa68d40c66e056b8998fb69af49f4e18707f783be8d500b8957ce3a885662d27c
importlib-metadata==2.0.0 --hash=sha512:09ec4c718781e3ba6ed8024a094081ce530f30c1aa7df8f10729d64f17839bcc35dc3c94218209dbb6d133f3052d33cebe282bf7b53ba9646d1653ce62cdae3b
zipp==3.3.0 --hash=sha512:1c83f8958eb172083a42c3cd0745e0c32def319992dfb227b906a9d0a9fd6f9dd556de49a8a84f76e27b0ebce7abe6a9a7fd12ae532e86898046c68e68d28f11
six==1.15.0 --hash=sha512:0416d59434623604de755601c919722c2b800042612a2a7b221ecd3ccf556aca3a78f0f926fd640032a3d74d153457628a89c25065dfcdbb96892d5bf7279904

10
requirements.txt Normal file → Executable file
View File

@ -5,15 +5,15 @@ pyserial==3.4 --hash=sha512:8333ac2843fd136d5d0d63b527b37866f7d18afc3bb33
# Argon2 Password Hashing Function (Derives keys that protect persistent user data)
argon2-cffi==20.1.0 --hash=sha512:4427657e9be95b4b68ec8d26e5571042068da3308b91ad82f289cfe94de196ecef71f437cf3f2e8f106fb7e743d85a69f24eece5257393e8bf5b1a6bbf9286cc
cffi==1.14.0 --hash=sha512:5b315a65fc8f40622ceef35466546620aaca9dd304f5491a845239659b4066469c5fb3f1683c382eb57f8975caf318e5d88852e3dbb049cde193c9189b88c9c0 \
--hash=sha512:af327aaee006c4d76034c6df9ffc2d7dd5d9fface5117007065db2bfef842ade3aae36727ff317c3e7f735facdfb5380e646bcd9615b14acfd2701a84c0d15ec
cffi==1.14.3 --hash=sha512:d906b00752bce5634b309574a3edd5fbb737cb90df351cedcb7c264f3a5b93e28bdd16af1ff69eeddce745b1ada93d97c0b7fb73e04e6973fd7631850cba5f87 \
--hash=sha512:4fac15b1ef79c668f2e9e4fe9b71ca181b3d1ec61ca5ccd4608843f8c841ab58b460d3cb01b242f61d996351ab0fb50d686adf488f89866f6cbf796a2d333ded
pycparser==2.20 --hash=sha512:06dc9cefdcde6b97c96d0452a77db42a629c48ee545edd7ab241763e50e3b3c56d21f9fcce4e206817aa1a597763d948a10ccc73572490d739c89eea7fede0a1
six==1.15.0 --hash=sha512:0416d59434623604de755601c919722c2b800042612a2a7b221ecd3ccf556aca3a78f0f926fd640032a3d74d153457628a89c25065dfcdbb96892d5bf7279904
# cryptography (pyca) (Handles TCB-side X448 key exchange)
cryptography==2.9.2 --hash=sha512:251d1ce022ac969516e54eae62b383bc113cc023a5459a030fa4c3d3d67c5ff4daa5d23bcf6a334845315ab71532e7aa3db28c882bbfed5260dd1ab01429ca6a \
--hash=sha512:3fd9dbc776bb9ff015b96174aa424d4a33c60df7f2b07a35c16c080bfaca06fe35466b02a8586157979abe15c69354923c44c9ab238b850de8bdb1b45814f0bd
cryptography==3.1.1 --hash=sha512:3f208fbc6954a3c6e8dc1d1c20ff2c32f4154eac5cbbc4b0c96032cc33be73bfd99081eaba9eb1557b3ffa3dfcb5312f77fff1393bfb50ce2e8df7a8c585f128 \
--hash=sha512:b6ab18c7ec3dc666c602d40d2b7626c09c9f78aafab62f340ee8f63b54a12c952c0031bb1e3a31bf9182e139ae58655b2f20056511ae95123981ccacda5bf855
# PyNaCl (pyca) (Handles TCB-side XChaCha20-Poly1305 symmetric encryption)
PyNaCl==1.4.0 --hash=sha512:bf1bb46d23419cb375bcf620a37b5e9ce925cb0dd55eadf851a4bbb9039c8846ed13ae33966436a96655ea41ad1fc282f9139a958fd55ea10597fd3859635a2f
setuptools==47.3.1 --hash=sha512:c86448d2348b4f58e3eb4c55f8133675f3a20315ee11e829a55f414c07c05f84afe4991d95625a8f0ed62e924b34bff29fd8e67a6929298ec53f69e6fcc4454b
setuptools==50.3.0 --hash=sha512:258cdd8b4bd49dcddd0097c2baf93be93dbffb1634ca1a984b5b71f84a0b37a8342c5725a06615bc8f0090c7193f24740db2f5c15a5b9b00f452fe0b14640c1c

View File

@ -331,17 +331,26 @@ class X448(object):
which then calls the `activate_osrandom_engine()` instance
method[4].
[1] https://github.com/pyca/cryptography/blob/3.1.1/src/cryptography/hazmat/primitives/asymmetric/x448.py#L39
[2] https://github.com/pyca/cryptography/blob/3.1.1/src/cryptography/hazmat/backends/openssl/backend.py#L2708
[3] https://github.com/pyca/cryptography/blob/3.1.1/src/cryptography/hazmat/backends/openssl/backend.py#L222
[4] https://github.com/pyca/cryptography/blob/3.1.1/src/cryptography/hazmat/backends/openssl/backend.py#L238
---
If the OpenSSL version is older than 1.1.1d:
3. Calling the `activate_osrandom_engine()` disables the default
OpenSSL CSPRNG, and activates the pyca/cryptography
"OS random engine".[5]
4. Unlike the OpenSSL user-space CSPRNG that only seeds from
4. Unlike the old OpenSSL user-space CSPRNG that only seeds from
/dev/urandom, the OS random engine uses the GETRANDOM(0)
syscall that sources all of its entropy directly from the
LRNG's ChaCha20 DRNG. The OS random engine does not suffer
from the fork() weakness where forked process is not
from the fork-weakness where forked process is not
automatically reseeded, and it's also safe from issues with
OpenSSL CSPRNG initialization.[6]
OpenSSL's CSPRNG initialization.[6]
5. The fallback option (/dev/urandom) of OS random engine might
be problematic on pre-3.17 kernels if the CSPRNG has not been
@ -355,14 +364,47 @@ class X448(object):
fully seeded. This is the same case as with TFC's `csprng()`
function.
[1] https://github.com/pyca/cryptography/blob/2.8/src/cryptography/hazmat/primitives/asymmetric/x448.py#L38
[2] https://github.com/pyca/cryptography/blob/2.8/src/cryptography/hazmat/backends/openssl/backend.py#L2483
[3] https://github.com/pyca/cryptography/blob/2.8/src/cryptography/hazmat/backends/openssl/backend.py#L118
[4] https://github.com/pyca/cryptography/blob/2.8/src/cryptography/hazmat/backends/openssl/backend.py#L125
[5] https://cryptography.io/en/latest/hazmat/backends/openssl/#activate_osrandom_engine
[6] https://cryptography.io/en/latest/hazmat/backends/openssl/#os-random-engine
[7] https://cryptography.io/en/latest/hazmat/backends/openssl/#os-random-sources
[8] https://github.com/pyca/cryptography/blob/master/src/_cffi_src/openssl/src/osrandom_engine.c#L395
[8] https://github.com/pyca/cryptography/blob/3.1.1/src/_cffi_src/openssl/src/osrandom_engine.c#L396
---
If the OpenSSL version used is 1.1.1d or newer:
3. The Backend init method calls[9] the
`activate_osrandom_engine` method, which will check[10]
whether OS Random Engine is needed, and since with
OpenSSL 1.1.1d+ it is not, the condition check will evaluate
as `False`, which in turn means the entire method is skipped.
4. The `generate` method will then call the
`backend.x448_generate_key()`[11] which in turn will call
the `_evp_pkey_keygen_gc` method[12].
7. The `_evp_pkey_keygen_gc` calls the `EVP_PKEY_keygen`
method[13], that given the context will generate the X448
private key.
8. To quote OpenSSL's change log[14]:
"On older Linux systems where the getrandom() system call is
not available, OpenSSL normally uses the /dev/urandom device
for seeding its CSPRNG. Contrary to getrandom(), the
/dev/urandom device will not block during early boot when
the kernel CSPRNG has not been seeded yet."
Again, as TFC checks that the kernel version of the OS it's
running on is at least 4.17, the entropy source used by
OpenSSL is always GETRANDOM(0).
[9] https://github.com/pyca/cryptography/blob/3.1.1/src/cryptography/hazmat/backends/openssl/backend.py#L238
[10] https://github.com/pyca/cryptography/blob/3.1.1/src/cryptography/hazmat/backends/openssl/backend.py#L288
[11] https://github.com/pyca/cryptography/blob/3.1.1/src/cryptography/hazmat/primitives/asymmetric/x448.py#L46
[12] https://github.com/pyca/cryptography/blob/3.1.1/src/cryptography/hazmat/backends/openssl/backend.py#L2365
[13] https://github.com/pyca/cryptography/blob/3.1.1/src/cryptography/hazmat/backends/openssl/backend.py#L2326
[14] https://www.openssl.org/news/changelog.html#openssl-111
"""
return X448PrivateKey.generate()

40
src/common/database.py Normal file → Executable file
View File

@ -30,7 +30,7 @@ import nacl.exceptions
from src.common.crypto import auth_and_decrypt, blake2b, encrypt_and_sign
from src.common.exceptions import CriticalError
from src.common.misc import ensure_dir, separate_trailer
from src.common.statics import BLAKE2_DIGEST_LENGTH, DB_WRITE_RETRY_LIMIT, DIR_USER_DATA, TEMP_POSTFIX
from src.common.statics import BLAKE2_DIGEST_LENGTH, DB_WRITE_RETRY_LIMIT, DIR_USER_DATA, TEMP_SUFFIX
if typing.TYPE_CHECKING:
from src.common.db_masterkey import MasterKey
@ -45,7 +45,7 @@ class TFCDatabase(object):
def __init__(self, database_name: str, master_key: 'MasterKey') -> None:
"""Initialize TFC database."""
self.database_name = database_name
self.database_temp = database_name + TEMP_POSTFIX
self.database_temp = database_name + TEMP_SUFFIX
self.database_key = master_key.master_key
@staticmethod
@ -92,13 +92,15 @@ class TFCDatabase(object):
ensure_dir(DIR_USER_DATA)
self.ensure_temp_write(ct_bytes)
# Replace the original file with a temp file. (`os.replace` is atomic as per
# POSIX requirements): https://docs.python.org/3/library/os.html#os.replace
if replace:
self.replace_database()
def replace_database(self) -> None:
"""Replace database with temporary database."""
"""Replace database with temporary database.
Replace the original file with a temp file. (`os.replace` is atomic as per
POSIX requirements): https://docs.python.org/3/library/os.html#os.replace
"""
os.replace(self.database_temp, self.database_name)
def load_database(self) -> bytes:
@ -114,7 +116,7 @@ class TFCDatabase(object):
"""
if os.path.isfile(self.database_temp):
if self.verify_file(self.database_temp):
os.replace(self.database_temp, self.database_name)
self.replace_database()
else:
# If temp file is not authentic, the file is most likely corrupt, so
# we delete it and continue using the old file to ensure atomicity.
@ -134,7 +136,7 @@ class TFCUnencryptedDatabase(object):
def __init__(self, database_name: str) -> None:
"""Initialize unencrypted TFC database."""
self.database_name = database_name
self.database_temp = database_name + TEMP_POSTFIX
self.database_temp = database_name + TEMP_SUFFIX
@staticmethod
def write_to_file(file_name: str, data: bytes) -> None:
@ -173,7 +175,6 @@ class TFCUnencryptedDatabase(object):
digest of the database content to the database file.
"""
ensure_dir(DIR_USER_DATA)
self.ensure_temp_write(data + blake2b(data))
# Replace the original file with a temp file. (`os.replace` is atomic as per
@ -181,7 +182,11 @@ class TFCUnencryptedDatabase(object):
os.replace(self.database_temp, self.database_name)
def replace_database(self) -> None:
"""Replace database with temporary database."""
"""Replace database with temporary database.
Replace the original file with a temp file. (`os.replace` is atomic as per
POSIX requirements): https://docs.python.org/3/library/os.html#os.replace
"""
if os.path.isfile(self.database_temp):
os.replace(self.database_temp, self.database_name)
@ -220,7 +225,7 @@ class MessageLog(object):
def __init__(self, database_name: str, database_key: bytes) -> None:
"""Create a new MessageLog object."""
self.database_name = database_name
self.database_temp = self.database_name + TEMP_POSTFIX
self.database_temp = self.database_name + TEMP_SUFFIX
self.database_key = database_key
ensure_dir(DIR_USER_DATA)
@ -233,10 +238,19 @@ class MessageLog(object):
def __iter__(self) -> Iterator[bytes]:
"""Iterate over encrypted log entries."""
for log_entry in self.c.execute("SELECT log_entry FROM log_entries"):
plaintext = auth_and_decrypt(log_entry[0], self.database_key, database=self.database_name)
for ct_log_entry in self.c.execute("SELECT log_entry FROM log_entries"):
plaintext = auth_and_decrypt(ct_log_entry[0], self.database_key, database=self.database_name)
yield plaintext
def replace_database(self) -> None:
"""Replace database with temporary database.
Replace the original file with a temp file. (`os.replace` is atomic as per
POSIX requirements): https://docs.python.org/3/library/os.html#os.replace
"""
if os.path.isfile(self.database_temp):
os.replace(self.database_temp, self.database_name)
def verify_file(self, database_name: str) -> bool:
"""Verify integrity of database file content."""
conn = sqlite3.connect(database_name)
@ -259,7 +273,7 @@ class MessageLog(object):
""""Check if temporary log database exists."""
if os.path.isfile(self.database_temp):
if self.verify_file(self.database_temp):
os.replace(self.database_temp, self.database_name)
self.replace_database()
else:
# If temp file failed integrity check, the file is most likely corrupt,
# so we delete it and continue using the old file to ensure atomicity.

8
src/common/db_logs.py Normal file → Executable file
View File

@ -38,7 +38,7 @@ from src.common.statics import (ASSEMBLY_PACKET_HEADER_LENGTH, DIR_USER_DATA,
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, TEMP_POSTFIX, TIMESTAMP_LENGTH, TRAFFIC_MASKING_QUEUE, TX,
P_N_HEADER, RX, TEMP_SUFFIX, TIMESTAMP_LENGTH, TRAFFIC_MASKING_QUEUE, TX,
UNIT_TEST_QUEUE, WHISPER_FIELD_LENGTH, WIN_TYPE_CONTACT, WIN_TYPE_GROUP)
from src.receiver.packet import Packet, PacketList
@ -338,7 +338,7 @@ def change_log_db_key(old_key: bytes,
"""Re-encrypt the log database with a new master key."""
ensure_dir(DIR_USER_DATA)
file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
temp_name = file_name + TEMP_POSTFIX
temp_name = file_name + TEMP_SUFFIX
if not os.path.isfile(file_name):
raise SoftError("No log database available.")
@ -360,7 +360,7 @@ def replace_log_db(settings: 'Settings') -> None:
"""Replace the log database with the temp file."""
ensure_dir(DIR_USER_DATA)
file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
temp_name = file_name + TEMP_POSTFIX
temp_name = file_name + TEMP_SUFFIX
if os.path.isfile(temp_name):
os.replace(temp_name, file_name)
@ -382,7 +382,7 @@ def remove_logs(contact_list: 'ContactList',
"""
ensure_dir(DIR_USER_DATA)
file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
temp_name = file_name + TEMP_POSTFIX
temp_name = file_name + TEMP_SUFFIX
packet_list = PacketList(settings, contact_list)
entries_to_keep = [] # type: List[bytes]
removed = False

154
src/common/gateway.py Normal file → Executable file
View File

@ -27,6 +27,7 @@ import os
import os.path
import serial
import socket
import subprocess
import textwrap
import time
import typing
@ -37,17 +38,17 @@ from typing import Any, Dict, Optional, Tuple, Union
from serial.serialutil import SerialException
from src.common.exceptions import CriticalError, graceful_exit, SoftError
from src.common.input import box_input, yes
from src.common.input import yes
from src.common.misc import (calculate_race_condition_delay, ensure_dir, ignored, get_terminal_width,
separate_trailer, split_byte_string, validate_ip_address)
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 (BAUDS_PER_BYTE, DIR_USER_DATA, DONE, DST_DD_LISTEN_SOCKET, DST_LISTEN_SOCKET,
GATEWAY_QUEUE, LOCALHOST, LOCAL_TESTING_PACKET_DELAY, MAX_INT, NC,
QUBES_DST_LISTEN_SOCKET, QUBES_RX_IP_ADDR_FILE, QUBES_SRC_LISTEN_SOCKET,
PACKET_CHECKSUM_LENGTH, RECEIVER, RELAY, RP_LISTEN_SOCKET, RX,
SERIAL_RX_MIN_TIMEOUT, SETTINGS_INDENT, SOCKET_BUFFER_SIZE, SRC_DD_LISTEN_SOCKET,
TRANSMITTER, TX, US_BYTE)
from src.common.statics import (BAUDS_PER_BYTE, BUFFER_FILE_DIR, BUFFER_FILE_NAME, DIR_USER_DATA, DONE,
DST_DD_LISTEN_SOCKET, DST_LISTEN_SOCKET, GATEWAY_QUEUE, LOCALHOST,
LOCAL_TESTING_PACKET_DELAY, MAX_INT, NC, PACKET_CHECKSUM_LENGTH, QUBES_DST_VM_NAME,
QUBES_NET_DST_POLICY, QUBES_NET_VM_NAME, QUBES_SRC_NET_POLICY, 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
@ -94,8 +95,6 @@ class Gateway(object):
self.rx_serial = None # type: Optional[serial.Serial]
self.rx_socket = None # type: Optional[multiprocessing.connection.Connection]
self.tx_socket = None # type: Optional[multiprocessing.connection.Connection]
self.txq_socket = None # type: Optional[socket.socket]
self.rxq_socket = None # type: Optional[socket.socket]
# Initialize Reed-Solomon erasure code handler
self.rs = RSCodec(2 * self.settings.session_serial_error_correction)
@ -109,12 +108,7 @@ class Gateway(object):
self.client_establish_socket()
if self.settings.software_operation in [NC, RX]:
self.server_establish_socket()
elif qubes:
if self.settings.software_operation in [TX, NC]:
self.qubes_client_establish_socket()
if self.settings.software_operation in [NC, RX]:
self.qubes_server_establish_socket()
else:
elif not self.settings.qubes:
self.establish_serial()
def establish_serial(self) -> None:
@ -153,18 +147,22 @@ class Gateway(object):
except SerialException:
raise CriticalError("SerialException. Ensure $USER is in the dialout group by restarting this computer.")
def write_udp_packet(self, packet: bytes) -> None:
"""Split packet to smaller parts and transmit them over the socket."""
udp_port = QUBES_SRC_LISTEN_SOCKET if self.settings.software_operation == TX else QUBES_DST_LISTEN_SOCKET
def send_over_qrexec(self, packet: bytes) -> None:
"""Send packet content over the Qubes qrexec RPC.
packet = base64.b85encode(packet)
packets = split_byte_string(packet, SOCKET_BUFFER_SIZE)
More information at https://www.qubes-os.org/doc/qrexec/
if self.txq_socket is not None:
for p in packets:
self.txq_socket.sendto(p, (self.settings.rx_udp_ip, udp_port))
time.sleep(0.000001)
self.txq_socket.sendto(US_BYTE, (self.settings.rx_udp_ip, udp_port))
The packet is encoded with ASCII85 to ensure e.g. 0x0a
byte is not interpreted as line feed by the RPC service.
"""
target_vm = QUBES_NET_VM_NAME if self.settings.software_operation == TX else QUBES_DST_VM_NAME
dom0_policy = QUBES_SRC_NET_POLICY if self.settings.software_operation == TX else QUBES_NET_DST_POLICY
subprocess.Popen(['/usr/bin/qrexec-client-vm', target_vm, dom0_policy],
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
).communicate(base64.b85encode(packet))
def write(self, orig_packet: bytes) -> None:
"""Add error correction data and output data via socket/serial interface.
@ -183,8 +181,8 @@ class Gateway(object):
except BrokenPipeError:
raise CriticalError("Relay IPC server disconnected.", exit_code=0)
elif self.txq_socket is not None:
self.write_udp_packet(packet)
elif self.settings.qubes:
self.send_over_qrexec(packet)
elif self.tx_serial is not None:
try:
@ -209,23 +207,36 @@ class Gateway(object):
except EOFError:
raise CriticalError("Relay IPC client disconnected.", exit_code=0)
def read_qubes_socket(self) -> bytes:
"""Read packet from Qubes' socket interface."""
if self.rxq_socket is None:
raise CriticalError("Socket interface has not been initialized.")
@staticmethod
def read_qubes_buffer_file(buffer_file_dir: str = '') -> bytes:
"""Read packet from oldest buffer file."""
buffer_file_dir = buffer_file_dir if buffer_file_dir else BUFFER_FILE_DIR
while True:
try:
read_buffer = bytearray()
ensure_dir(f"{buffer_file_dir}/")
while True:
read = self.rxq_socket.recv(SOCKET_BUFFER_SIZE)
if read == US_BYTE:
return read_buffer
read_buffer.extend(read)
while not any([f for f in os.listdir(buffer_file_dir) if f.startswith(BUFFER_FILE_NAME)]):
time.sleep(0.001)
except (EOFError, KeyboardInterrupt):
pass
tfc_buffer_file_numbers = [f[(len(BUFFER_FILE_NAME)+len('.')):] for f in os.listdir(buffer_file_dir) if f.startswith(BUFFER_FILE_NAME)]
tfc_buffer_file_numbers = [n for n in tfc_buffer_file_numbers if n.isdigit()]
tfc_buffer_files_in_order = [f"{BUFFER_FILE_NAME}.{n}" for n in sorted(tfc_buffer_file_numbers, key=int)]
try:
oldest_buffer_file = tfc_buffer_files_in_order[0]
except IndexError:
raise SoftError("No packet was available.", output=False)
with open(f"{buffer_file_dir}/{oldest_buffer_file}", 'rb') as f:
packet = f.read()
try:
packet = base64.b85decode(packet)
except ValueError:
raise SoftError("Error: Received packet had invalid Base85 encoding.")
os.remove(f"{buffer_file_dir}/{oldest_buffer_file}")
return packet
def read_serial(self) -> bytes:
"""Read packet from serial interface.
@ -260,12 +271,12 @@ class Gateway(object):
except (OSError, SerialException):
self.establish_serial()
def read(self) -> bytes:
def read(self, buffer_file_dir: str = '') -> bytes:
"""Read data via socket/serial interface."""
if self.settings.local_testing_mode:
return self.read_socket()
if self.settings.qubes:
return self.read_qubes_socket()
return self.read_qubes_buffer_file(buffer_file_dir)
return self.read_serial()
def add_error_correction(self, packet: bytes) -> bytes:
@ -305,6 +316,7 @@ class Gateway(object):
raise SoftError("Error: Reed-Solomon failed to correct errors in the received packet.", bold=True)
else:
packet, checksum = separate_trailer(packet, PACKET_CHECKSUM_LENGTH)
if hashlib.blake2b(packet, digest_size=PACKET_CHECKSUM_LENGTH).digest() != checksum:
raise SoftError("Warning! Received packet had an invalid checksum.", bold=True)
return packet
@ -338,30 +350,6 @@ class Gateway(object):
return f'/dev/{self.settings.built_in_serial_interface}'
raise CriticalError(f"Error: /dev/{self.settings.built_in_serial_interface} was not found.")
# Qubes
def qubes_client_establish_socket(self) -> None:
"""Establish Qubes socket for outgoing data."""
self.txq_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def qubes_server_establish_socket(self) -> None:
"""Establish Qubes socket for incoming data."""
udp_port = QUBES_SRC_LISTEN_SOCKET if self.settings.software_operation == NC else QUBES_DST_LISTEN_SOCKET
self.rxq_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.rxq_socket.bind((self.get_local_ip_addr(), udp_port))
@staticmethod
def get_local_ip_addr() -> str:
"""Get local IP address of the system."""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('192.0.0.8', 1027))
except socket.error:
raise CriticalError("Socket error")
ip_address = s.getsockname()[0] # type: str
return ip_address
# Local testing
def server_establish_socket(self) -> None:
@ -462,7 +450,6 @@ class GatewaySettings(object):
self.serial_error_correction = 5
self.use_serial_usb_adapter = True
self.built_in_serial_interface = 'ttyS0'
self.rx_udp_ip = ''
self.software_operation = operation
self.local_testing_mode = local_test
@ -529,22 +516,6 @@ class GatewaySettings(object):
m_print(f"Error: Serial interface /dev/{self.built_in_serial_interface} not found.")
self.setup()
if self.qubes and self.software_operation != RX:
# Check if IP address was stored by the installer.
if os.path.isfile(QUBES_RX_IP_ADDR_FILE):
cached_ip = open(QUBES_RX_IP_ADDR_FILE).read().strip()
os.remove(QUBES_RX_IP_ADDR_FILE)
if validate_ip_address(cached_ip) == '':
self.rx_udp_ip = cached_ip
return
# If we reach this point, no cached IP was found, prompt for IP address from the user.
rx_device, short = ('Networked', 'NET') if self.software_operation == TX else ('Destination', 'DST')
m_print(f"Enter the IP address of the {rx_device} Computer", head=1, tail=1)
self.rx_udp_ip = box_input(f"{short} IP-address", expected_len=15, validator=validate_ip_address, tail=1)
def store_settings(self) -> None:
"""Store serial settings in JSON format."""
serialized = json.dumps(self, default=(lambda o: {k: self.__dict__[k] for k in self.key_list}), indent=4)
@ -600,9 +571,6 @@ class GatewaySettings(object):
elif key == 'built_in_serial_interface':
self.validate_serial_interface_value(key, json_dict)
elif key == 'rx_udp_ip':
json_dict[key] = self.validate_rx_udp_ip_address(key, json_dict)
except SoftError:
continue
@ -644,16 +612,6 @@ class GatewaySettings(object):
self.invalid_setting(key, json_dict)
raise SoftError("Invalid value", output=False)
def validate_rx_udp_ip_address(self, key: str, json_dict: Any) -> str:
"""Validate IP address of receiving Qubes VM."""
if self.qubes:
if not isinstance(json_dict[key], str) or validate_ip_address(json_dict[key]) != '':
self.setup()
return self.rx_udp_ip
rx_udp_ip = json_dict[key] # type: str
return rx_udp_ip
def change_setting(self, key: str, value_str: str) -> None:
"""Parse, update and store new setting value."""
attribute = self.__getattribute__(key)

14
src/common/statics.py Normal file → Executable file
View File

@ -21,7 +21,7 @@ along with TFC. If not, see <https://www.gnu.org/licenses/>.
"""Program details"""
TFC = 'TFC'
VERSION = '1.20.07'
VERSION = '1.20.10'
TRANSMITTER = 'Transmitter'
RECEIVER = 'Receiver'
RELAY = 'Relay'
@ -404,7 +404,7 @@ TRAFFIC_MASKING = 'traffic_masking'
DIR_USER_DATA = 'user_data/'
DIR_RECV_FILES = 'received_files/'
DIR_TFC = 'tfc/'
TEMP_POSTFIX = '_temp'
TEMP_SUFFIX = '_temp'
"""Key exchange status states"""
@ -504,10 +504,12 @@ DD_ANIMATION_LENGTH = 16
DD_OFFSET_FROM_CENTER = 4
# Qubes related
QUBES_SRC_LISTEN_SOCKET = 2063
QUBES_DST_LISTEN_SOCKET = 2064
SOCKET_BUFFER_SIZE = 4096
QUBES_RX_IP_ADDR_FILE = 'rx_ip_addr'
QUBES_NET_VM_NAME = 'TFC-Networker'
QUBES_DST_VM_NAME = 'TFC-Destination'
QUBES_SRC_NET_POLICY = 'tfc.SourceNetworker'
QUBES_NET_DST_POLICY = 'tfc.NetworkerDestination'
BUFFER_FILE_DIR = '/home/user/tfc/.buffered_incoming_packets'
BUFFER_FILE_NAME = 'buffered_incoming_packet'
# Field lengths
ENCODED_BOOLEAN_LENGTH = 1

View File

@ -267,7 +267,7 @@ def process_key_management_command(queues: 'QueueDict', key_list: 'KeyList') ->
if km_queue.qsize():
key_list.manage(queues, *km_queue.get())
SoftError("Key management command processing complete.", output=False)
raise SoftError("Key management command processing complete.", output=False)
def process_command(queues: 'QueueDict',
@ -281,7 +281,7 @@ def process_command(queues: 'QueueDict',
if c_queue.qsize():
if key_list.has_local_keyset():
send_packet(key_list, gateway, log_queue, c_queue.get())
SoftError("Command processing complete.", output=False)
raise SoftError("Command processing complete.", output=False)
def process_relay_packets(queues: 'QueueDict', gateway: 'Gateway') -> None:
@ -298,7 +298,7 @@ def process_relay_packets(queues: 'QueueDict', gateway: 'Gateway') -> None:
time.sleep(gateway.settings.data_diode_sockets * 1.5)
signal = WIPE if command == UNENCRYPTED_WIPE_COMMAND else EXIT
queues[EXIT_QUEUE].put(signal)
SoftError("Relay packet processing complete.", output=False)
raise SoftError("Relay packet processing complete.", output=False)
def process_buffered_messages(m_buffer: 'MessageBuffer',

2
tests/common/test_crypto.py Normal file → Executable file
View File

@ -202,7 +202,7 @@ class TestArgon2KDF(unittest.TestCase):
# Verify the SHA256 hash of the zip-file containing the command-line utility.
with open(file_name, 'rb') as f:
file_data = f.read()
self.assertEqual('ff8075cfbca9e9d892ee111c7776688e7edf4abe49c26cc0211a75143f4bf016',
self.assertEqual('370bc1e1ed0469e5ab77d54a26277bfb711ba9f64658af50c784d961a2bdaca8',
hashlib.sha256(file_data).hexdigest())
# Unzip, compile, and test the command-line utility.

145
tests/common/test_gateway.py Normal file → Executable file
View File

@ -23,6 +23,8 @@ import base64
import os
import unittest
import socket
import threading
import time
from datetime import datetime
from unittest import mock
@ -35,8 +37,8 @@ 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 (DIR_USER_DATA, GATEWAY_QUEUE, NC, PACKET_CHECKSUM_LENGTH, QUBES_RX_IP_ADDR_FILE,
RX, TX, US_BYTE)
from src.common.statics import (BUFFER_FILE_NAME, DIR_USER_DATA, GATEWAY_QUEUE, NC, PACKET_CHECKSUM_LENGTH, RX, TX,
QUBES_DST_VM_NAME, QUBES_NET_VM_NAME, QUBES_NET_DST_POLICY, QUBES_SRC_NET_POLICY)
from tests.mock_classes import Settings
from tests.utils import cd_unit_test, cleanup, gen_queue_dict, tear_queues, TFCTestCase
@ -279,54 +281,54 @@ class TestGatewaySerial(TFCTestCase):
gateway.write(b'data')
# Qubes
@mock.patch('time.sleep', return_value=None)
@mock.patch('socket.socket', MagicMock(return_value=MagicMock(
recv=MagicMock(side_effect=[EOFError, b'data', US_BYTE]))))
def test_qubes_socket_server(self, *_: Any) -> None:
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=True)
self.assertIsInstance(gateway, Gateway)
self.assertEqual(gateway.read(), b'data')
@mock.patch('time.sleep', return_value=None)
@mock.patch('socket.socket', MagicMock(return_value=MagicMock(
recv=MagicMock(side_effect=[EOFError, b'data', US_BYTE]))))
def test_qubes_socket_server_raises_critical_error_if_interface_is_not_initialized(self, *_: Any) -> None:
def test_qubes_read_file(self, *_: Any) -> None:
# Setup
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=True)
gateway.rxq_socket = None
buffer_file_dir = os.getcwd()
ensure_dir(f"{buffer_file_dir}/")
def packet_delayer() -> None:
"""Create packets one at a time."""
time.sleep(0.1)
with open(f"{buffer_file_dir}/{BUFFER_FILE_NAME}.invalid", 'wb+') as f:
f.write(base64.b85encode(b'data'))
time.sleep(0.1)
with open(f"{buffer_file_dir}/{BUFFER_FILE_NAME}.0", 'wb+') as f:
f.write(base64.b85encode(b'data'))
threading.Thread(target=packet_delayer).start()
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=True)
# Test
with self.assertRaises(SystemExit):
self.assertEqual(gateway.read(), b'data')
self.assert_se("No packet was available.", gateway.read, buffer_file_dir)
@mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
@mock.patch('socket.socket', MagicMock())
def test_qubes_socket_client(self, *_: Any) -> None:
time.sleep(0.3)
self.assertIsInstance(gateway, Gateway)
self.assertEqual(gateway.read(buffer_file_dir), b'data')
# Test invalid packet content is handled
with open(f"{buffer_file_dir}/{BUFFER_FILE_NAME}.1", 'wb+') as f:
f.write(os.urandom(32))
self.assert_se("Error: Received packet had invalid Base85 encoding.", gateway.read, buffer_file_dir)
@mock.patch('subprocess.Popen')
def test_qubes_send_to_networkerVM(self, mock_popen) -> None:
gateway = Gateway(operation=TX, local_test=False, dd_sockets=False, qubes=True)
self.assertIsInstance(gateway, Gateway)
self.assertIsNone(gateway.write(b'data'))
mock_popen.assert_called_with(['/usr/bin/qrexec-client-vm', QUBES_NET_VM_NAME, QUBES_SRC_NET_POLICY], stderr=-3, stdin=-1, stdout=-3)
@mock.patch('time.sleep', return_value=None)
@mock.patch('socket.socket', MagicMock())
def test_qubes_auto_config_from_file(self, *_: Any) -> None:
# Setup
test_ip = '10.137.0.17'
open(QUBES_RX_IP_ADDR_FILE, 'w+').write(test_ip)
@mock.patch('subprocess.Popen')
def test_qubes_send_to_destinationVM(self, mock_popen) -> None:
gateway = Gateway(operation=NC, local_test=False, dd_sockets=False, qubes=True)
self.assertIsInstance(gateway, Gateway)
self.assertIsNone(gateway.write(b'data'))
mock_popen.assert_called_with(['/usr/bin/qrexec-client-vm', QUBES_DST_VM_NAME, QUBES_NET_DST_POLICY], stderr=-3, stdin=-1, stdout=-3)
# Test
self.assertTrue(os.path.isfile(QUBES_RX_IP_ADDR_FILE))
gateway = Gateway(operation=TX, local_test=False, dd_sockets=False, qubes=True)
self.assertEqual(gateway.settings.rx_udp_ip, test_ip)
self.assertFalse(os.path.isfile(QUBES_RX_IP_ADDR_FILE))
@mock.patch('time.sleep', return_value=None)
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
@mock.patch('socket.socket', MagicMock(return_value=MagicMock(connect=MagicMock(side_effect=[socket.error]))))
def test_socket_error_raises_critical_error(self, *_: Any) -> None:
gateway = Gateway(operation=TX, local_test=False, dd_sockets=False, qubes=True)
with self.assertRaises(SystemExit):
gateway.get_local_ip_addr()
class TestGatewaySettings(TFCTestCase):
@ -338,8 +340,7 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": 19200,
"serial_error_correction": 5,
"use_serial_usb_adapter": true,
"built_in_serial_interface": "ttyS0",
"rx_udp_ip": ""
"built_in_serial_interface": "ttyS0"
}"""
def tearDown(self) -> None:
@ -376,8 +377,7 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": 9600,
"serial_error_correction": 1,
"use_serial_usb_adapter": false,
"built_in_serial_interface": "ttyS0",
"rx_udp_ip": "10.137.0.17"
"built_in_serial_interface": "ttyS0"
}""")
# Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
@ -385,7 +385,6 @@ class TestGatewaySettings(TFCTestCase):
self.assertEqual(settings.serial_error_correction, 1)
self.assertEqual(settings.use_serial_usb_adapter, False)
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
self.assertEqual(settings.rx_udp_ip, '10.137.0.17')
def test_missing_values_are_set_to_default_and_database_is_overwritten(self) -> None:
# Setup
@ -435,8 +434,7 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": 19201,
"serial_error_correction": 5,
"use_serial_usb_adapter": true,
"built_in_serial_interface": "ttyS0",
"rx_udp_ip": ""
"built_in_serial_interface": "ttyS0"
}""")
# Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
@ -459,8 +457,7 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": 19200,
"serial_error_correction": -1,
"use_serial_usb_adapter": true,
"built_in_serial_interface": "ttyS0",
"rx_udp_ip": ""
"built_in_serial_interface": "ttyS0"
}""")
# Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
@ -483,8 +480,7 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": 19200,
"serial_error_correction": 5,
"use_serial_usb_adapter": true,
"built_in_serial_interface": "does_not_exist",
"rx_udp_ip": ""
"built_in_serial_interface": "does_not_exist"
}""")
# Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
@ -498,48 +494,6 @@ class TestGatewaySettings(TFCTestCase):
self.assertEqual(data, self.default_serialized)
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
def test_invalid_rx_udp_ip_is_replaced_with_user_input(self, _) -> None:
# Setup
ensure_dir(DIR_USER_DATA)
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
f.write("""\
{
"serial_baudrate": 19200,
"serial_error_correction": 5,
"use_serial_usb_adapter": true,
"built_in_serial_interface": "ttyS0",
"rx_udp_ip": "256.256.256.256"
}""")
# Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=True)
self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 5)
self.assertEqual(settings.use_serial_usb_adapter, True)
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
self.assertEqual(settings.rx_udp_ip, '10.137.0.17')
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
def test_invalid_rx_udp_ip_type_is_replaced_with_user_input(self, _) -> None:
# Setup
ensure_dir(DIR_USER_DATA)
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
f.write("""\
{
"serial_baudrate": 19200,
"serial_error_correction": 5,
"use_serial_usb_adapter": true,
"built_in_serial_interface": "ttyS0",
"rx_udp_ip": 5
}""")
# Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=True)
self.assertEqual(settings.serial_baudrate, 19200)
self.assertEqual(settings.serial_error_correction, 5)
self.assertEqual(settings.use_serial_usb_adapter, True)
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
self.assertEqual(settings.rx_udp_ip, '10.137.0.17')
def test_invalid_type_is_replaced_with_default(self) -> None:
# Setup
ensure_dir(DIR_USER_DATA)
@ -549,8 +503,7 @@ class TestGatewaySettings(TFCTestCase):
"serial_baudrate": "115200",
"serial_error_correction": "5",
"use_serial_usb_adapter": "true",
"built_in_serial_interface": true,
"rx_udp_ip": ""
"built_in_serial_interface": true
}""")
# Test
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)