0.16.1
This commit is contained in:
commit
9f571bdb74
|
@ -0,0 +1,260 @@
|
|||
#Licences
|
||||
|
||||
### C-programs
|
||||
|
||||
Copyrights of the design schematic for HW RNG belong to Giorgio Vazzana and
|
||||
are modified and published under GNU free license and written permission of the
|
||||
author.
|
||||
|
||||
### Data diode
|
||||
|
||||
Copyrights for the design schematics of the RS-232 Data Diode belong to
|
||||
Douglas W. Jones and are published used under GNU free license and used in
|
||||
this document accordingly.
|
||||
|
||||
### TFC Documents
|
||||
|
||||
Both the white paper and manual are released under GNU Free Documentation
|
||||
License 1.3
|
||||
|
||||
|
||||
### TFC Suite
|
||||
|
||||
Tx.py, Rx.py, NH.py, test_tx.py, test_nh.py, test_rx.py, hwrng.py, setup.py
|
||||
and dd.py are part of the TFC application, which is free software: You can
|
||||
redistribute it and/or modify it under the terms of the GNU General Public
|
||||
Licence as published by the Free Software Foundation, either version 3 of the
|
||||
Licence, or apart from specified sections[1], (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 Licence for more details. For a
|
||||
copy of the GNU General Public License, see http://www.gnu.org/licenses/
|
||||
|
||||
[1] print_banner() style 3 in Tx.py, Rx.py and NH.py are based on matrix curses
|
||||
by Tom Wallroth. This code is used, modified and published under GNU GPL v2
|
||||
licence.
|
||||
|
||||
##PyNaCl
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
|
||||
###NOTICES
|
||||
|
||||
Since making changes to crypto libraries is generally not a good idea,
|
||||
here is documentation that explains why each change was made and how
|
||||
they do not weaken security.
|
||||
|
||||
https://github.com/maqp/pynacl/commit/
|
||||
a89b862a703353fd536951eea574ef4305030a11#diff-ac3b39a5ab4db5864edbe34708be717d
|
||||
|
||||
Added a getter shared_key() to extract the ECDHE shared secret after key
|
||||
exchange; This allows Tx.py to generate two separate symmetric keys by
|
||||
generating them using PBKDF2-HMAC-SHA256, each key salted with a different
|
||||
public key. While nonce-based crypto would be secure with two parties
|
||||
using the same key, it would have no forward secrecy. TFC generates
|
||||
forward secrecy with cyclic hashing of key through PBKDF2-HMAC-SHA256
|
||||
between every message. Using only one key would mean any offset in state
|
||||
of key would lead to retrospective decryption of messages between the
|
||||
two states, if end points were physically compromised.
|
||||
|
||||
TFC has always included an option for Raspberry Pi and HWRNG generated keys.
|
||||
The edit to PrivateKey.generate() allows user to mix in entropy from the
|
||||
HWRNG during private key generation. External entropy is XORed with standard
|
||||
entropy of the library -- it has no adverse effects and can only add entropy
|
||||
to keys.
|
||||
|
||||
Simple proof of this is assume the external entropy is malicious. It could
|
||||
try to generate a keystream based on nacl.utils.random(), aiming to produce
|
||||
a final output that is a bit string of zeroes. This is not possible as
|
||||
external entropy is loaded before PyNaCl's CSPRNG random() is called.
|
||||
|
||||
Alternatively, the external entropy can be malicious, predictable bit
|
||||
sequence that provides no additional entropy. This is in no way different
|
||||
from XORing the random() function's output with arbitrary number of times
|
||||
with any completely predictable string, e.g. a bit-string of 0s, that has
|
||||
no effect on entropy random() returns. Therefore, the external entropy can
|
||||
not weaken the overall security.
|
||||
|
||||
This property is used when no external entropy is provided. In those cases,
|
||||
the output random() is in fact XORed with bit-string of zeroes, and it will
|
||||
lead to exact same output as that of random(). Since constant time XOR
|
||||
operation is performed for every generation time, it introduces practically
|
||||
no timing attacks. Also, in the case of TFC, adversary is unable to exploit
|
||||
TxM behind data diode, and obtain information about when the key generation
|
||||
started. Instead, they will first learn about the key generation when TxM
|
||||
outputs the public key to contact.
|
||||
|
|
@ -0,0 +1,988 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# TFC-NaCl 0.16.01 beta || NH.py
|
||||
|
||||
"""
|
||||
GPL License
|
||||
|
||||
This software is part of the TFC application, which 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. For
|
||||
a copy of the GNU General Public License, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from binascii import hexlify
|
||||
from datetime import datetime
|
||||
from dbus import Interface, SessionBus
|
||||
from dbus.exceptions import DBusException
|
||||
from dbus.mainloop.qt import DBusQtMainLoop
|
||||
from fcntl import ioctl
|
||||
from multiprocessing import Process, Queue
|
||||
from multiprocessing.connection import Client, Listener
|
||||
from os import listdir, system
|
||||
from random import choice, randrange, randint as sysrandint
|
||||
from serial import Serial, serialutil
|
||||
from struct import unpack
|
||||
from sys import argv, stdout
|
||||
from termios import TIOCGWINSZ
|
||||
from time import sleep
|
||||
import curses
|
||||
|
||||
from hashlib import sha256
|
||||
from PyQt4.QtGui import QApplication
|
||||
|
||||
|
||||
str_version = "0.16.01 beta"
|
||||
int_version = 1601
|
||||
|
||||
###############################################################################
|
||||
# CONFIGURATION #
|
||||
###############################################################################
|
||||
|
||||
# UI settings
|
||||
t_fmt = "%m-%d / %H:%M:%S" # Format of timestamps when displaying messages
|
||||
|
||||
startup_banner = True # False disables the animated startup banner
|
||||
|
||||
|
||||
# Local Testing
|
||||
local_testing = False # True enables testing of TFC on a single computer
|
||||
|
||||
dd_sockets = False # True changes sockets for data diode simulator
|
||||
|
||||
output_pidgin = True # False stops sending messages to Pidgin
|
||||
|
||||
|
||||
# Serial port settings
|
||||
baud_rate = 9600 # The serial interface speed
|
||||
|
||||
checksum_len = 8 # Data diode error detection rate. 8 hex = 32-bit
|
||||
|
||||
txm_usb_adapter = True # False = Use integrated serial interface for TxM
|
||||
|
||||
rxm_usb_adapter = True # False = Use integrated serial interface for RxM
|
||||
|
||||
|
||||
###############################################################################
|
||||
# ERROR CLASSES #
|
||||
###############################################################################
|
||||
|
||||
class CriticalError(Exception):
|
||||
|
||||
def __init__(self, function_name, error_message):
|
||||
system("clear")
|
||||
print("\nERROR: M(%s): %s\n" % (function_name, error_message))
|
||||
graceful_exit()
|
||||
|
||||
|
||||
class FunctionParameterTypeError(Exception):
|
||||
|
||||
def __init__(self, function_name):
|
||||
system("clear")
|
||||
print("\nERROR: M(%s): Wrong input type.\n" % function_name)
|
||||
graceful_exit()
|
||||
|
||||
|
||||
###############################################################################
|
||||
# HELPERS #
|
||||
###############################################################################
|
||||
|
||||
def phase(string, dist):
|
||||
"""
|
||||
Print name of next phase. Next message (about completion), printed after
|
||||
the phase will be printed on same line as the name specified by 'string'.
|
||||
|
||||
:param string: String to be printed.
|
||||
:param dist: Indentation of completion message.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if not isinstance(string, str) or not isinstance(dist, (int, long)):
|
||||
raise FunctionParameterTypeError("phase")
|
||||
|
||||
stdout.write(string + ((dist - len(string)) * ' '))
|
||||
stdout.flush()
|
||||
sleep(0.02)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def sha2_256(message):
|
||||
"""
|
||||
Generate SHA256 digest from message.
|
||||
|
||||
:param message: Input to hash function.
|
||||
:return: Hex representation of SHA256 digest.
|
||||
"""
|
||||
|
||||
if not isinstance(message, str):
|
||||
raise FunctionParameterTypeError("sha2_256")
|
||||
|
||||
h_function = sha256()
|
||||
h_function.update(message)
|
||||
hex_digest = hexlify(h_function.digest())
|
||||
|
||||
return hex_digest
|
||||
|
||||
|
||||
def verify_checksum(packet):
|
||||
"""
|
||||
Detect transmission errors by verifying SHA256-based checksum.
|
||||
|
||||
:param packet: Packet to calculate checksum for.
|
||||
:return: True if checksum was correct, else False.
|
||||
"""
|
||||
|
||||
if not isinstance(packet, str):
|
||||
raise FunctionParameterTypeError("verify_checksum")
|
||||
|
||||
chksum_pckt = packet[-checksum_len:]
|
||||
separated_p = packet[:-(checksum_len + 1)]
|
||||
chksum_calc = sha2_256(separated_p)[:checksum_len]
|
||||
|
||||
if chksum_calc == chksum_pckt:
|
||||
return True
|
||||
else:
|
||||
print("\nChecksum error: Command / message was discarded.\n"
|
||||
"If error persists, check TxM data diode batteries.\n")
|
||||
return False
|
||||
|
||||
|
||||
def clean_exit(message=''):
|
||||
"""
|
||||
Print exit message and close program.
|
||||
|
||||
:param message: Message to print.
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
if not isinstance(message, str):
|
||||
raise FunctionParameterTypeError("clean_exit")
|
||||
|
||||
system("clear")
|
||||
|
||||
if message:
|
||||
print("\n%s\n" % message)
|
||||
|
||||
print("\nExiting TFC-NaCl.\n")
|
||||
exit()
|
||||
|
||||
|
||||
def graceful_exit():
|
||||
"""
|
||||
Display a message and exit Tx.py.
|
||||
|
||||
If trickle connection is enabled, output a message to
|
||||
exit_queue so main loop can kill processes and exit.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if unittesting:
|
||||
raise SystemExit
|
||||
else:
|
||||
exit_queue.put("exit")
|
||||
sleep(1)
|
||||
|
||||
|
||||
def get_tty_wh():
|
||||
"""
|
||||
Get width and height of terminal.
|
||||
|
||||
:return: Width and height of terminal.
|
||||
"""
|
||||
|
||||
def ioctl_gwin_size(fd):
|
||||
"""
|
||||
No definition.
|
||||
|
||||
:param fd: [no definition]
|
||||
:return: [no definition]
|
||||
"""
|
||||
|
||||
return unpack("hh", ioctl(fd, TIOCGWINSZ, "1234"))
|
||||
|
||||
cr = ioctl_gwin_size(0) or ioctl_gwin_size(1) or ioctl_gwin_size(2)
|
||||
|
||||
return int(cr[1]), int(cr[0])
|
||||
|
||||
|
||||
def print_banner():
|
||||
"""
|
||||
Print animated startup banner.
|
||||
|
||||
Style 3:
|
||||
Matrix-Curses - See how deep the rabbit hole goes.
|
||||
Copyright (c) 2012 Tom Wallroth
|
||||
http://github.com/devsnd/matrix-curses/
|
||||
|
||||
Used and modified under GNU GPL version 3
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
string = "Tinfoil Chat NaCl %s" % str_version
|
||||
|
||||
system("clear")
|
||||
width, height = get_tty_wh()
|
||||
|
||||
print((height / 2) - 1) * '\n'
|
||||
|
||||
# Style 1
|
||||
animation = sysrandint(1, 3)
|
||||
if animation == 1:
|
||||
i = 0
|
||||
while i <= len(string):
|
||||
stdout.write("\x1b[1A" + ' ')
|
||||
stdout.flush()
|
||||
|
||||
if i == len(string):
|
||||
print((width - len(string)) / 2) * ' ' + string[:i]
|
||||
else:
|
||||
rc = chr(randrange(32, 126))
|
||||
print((width - len(string)) / 2) * ' ' + string[:i] + rc
|
||||
|
||||
i += 1
|
||||
sleep(0.03)
|
||||
|
||||
# Style 2
|
||||
if animation == 2:
|
||||
char_l = len(string) * ['']
|
||||
|
||||
while True:
|
||||
stdout.write("\x1b[1A" + ' ')
|
||||
stdout.flush()
|
||||
st = ''
|
||||
|
||||
for i in range(len(string)):
|
||||
if char_l[i] != string[i]:
|
||||
char_l[i] = chr(randrange(32, 126))
|
||||
else:
|
||||
char_l[i] = string[i]
|
||||
st += char_l[i]
|
||||
|
||||
print((width - len(string)) / 2) * ' ' + st
|
||||
|
||||
sleep(0.004)
|
||||
if st == string:
|
||||
break
|
||||
|
||||
# Style 3
|
||||
if animation == 3:
|
||||
|
||||
string = "Tinfoil Chat NaCl 0.16.1"
|
||||
dropping_chars = 50
|
||||
random_cleanup = 80
|
||||
min_speed = 3
|
||||
max_speed = 7
|
||||
sleep_ms = 0.005
|
||||
|
||||
scroll_chars = ''
|
||||
for a in range(32, 126):
|
||||
scroll_chars += chr(a)
|
||||
|
||||
class FChar(object):
|
||||
|
||||
list_chr = list(scroll_chars)
|
||||
normal_attr = curses.A_NORMAL
|
||||
highlight_attr = curses.A_REVERSE
|
||||
|
||||
def __init__(self, o_width, speed_min, speed_max):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.speed = 1
|
||||
self.char = ' '
|
||||
self.offset = randint(0, self.speed)
|
||||
self.reset(o_width, speed_min, speed_max)
|
||||
self.completed = []
|
||||
|
||||
def reset(self, c_width, speed_min, speed_max):
|
||||
self.char = choice(FChar.list_chr)
|
||||
self.x = randint(1, c_width - 1)
|
||||
self.y = 0
|
||||
self.speed = randint(speed_min, speed_max)
|
||||
self.offset = randint(0, self.speed)
|
||||
|
||||
def get_completed(self):
|
||||
return self.completed
|
||||
|
||||
def tick(self, scr, steps):
|
||||
win_h, win_w = scr.getmaxyx()
|
||||
if self.advances(steps):
|
||||
|
||||
# If window was re-sized and char is out of bounds, reset
|
||||
self.out_of_bounds_reset(win_w, win_h)
|
||||
|
||||
# Make previous char curses.A_NORMAL
|
||||
scr.addstr(self.y, self.x, self.char, curses.A_NORMAL)
|
||||
|
||||
# Choose new char and draw it A_NORMAL if not out of bounds
|
||||
self.y += 1
|
||||
if self.y == win_h / 2:
|
||||
indent_len = (win_w - len(string)) / 2
|
||||
prepended_ind = (indent_len * ' ')
|
||||
final_string = prepended_ind + string + ' '
|
||||
|
||||
if self.x > indent_len - 1:
|
||||
try:
|
||||
self.char = final_string[self.x]
|
||||
self.completed.append(self.x)
|
||||
except IndexError:
|
||||
self.char = choice(FChar.list_chr)
|
||||
else:
|
||||
self.char = choice(FChar.list_chr)
|
||||
|
||||
if not self.out_of_bounds_reset(win_w, win_h):
|
||||
scr.addstr(self.y, self.x, self.char, curses.A_NORMAL)
|
||||
|
||||
def out_of_bounds_reset(self, win_w, win_h):
|
||||
if self.x > win_w - 2:
|
||||
self.reset(win_w, min_speed, max_speed)
|
||||
return True
|
||||
if self.y > win_h - 2:
|
||||
self.reset(win_w, min_speed, max_speed)
|
||||
return True
|
||||
return False
|
||||
|
||||
def advances(self, steps):
|
||||
if steps % (self.speed + self.offset) == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
# Use insecure but fast PRNG
|
||||
def rand():
|
||||
p = sysrandint(0, 1000000000)
|
||||
while True:
|
||||
p ^= (p << 21) & 0xffffffffffffffff
|
||||
p ^= (p >> 35)
|
||||
p ^= (p << 4) & 0xffffffffffffffff
|
||||
yield p
|
||||
|
||||
def randint(_min, _max):
|
||||
n = r.next()
|
||||
return (n % (_max - _min)) + _min
|
||||
|
||||
def main():
|
||||
steps = 0
|
||||
scr = curses.initscr()
|
||||
scr.nodelay(1)
|
||||
curses.curs_set(0)
|
||||
curses.noecho()
|
||||
|
||||
win_h, win_w = scr.getmaxyx()
|
||||
|
||||
if win_w < len(string):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
window_animation = None
|
||||
lines = []
|
||||
|
||||
for _ in range(dropping_chars):
|
||||
fc = FChar(win_w, min_speed, max_speed)
|
||||
fc.y = randint(0, win_h - 2)
|
||||
lines.append(fc)
|
||||
|
||||
scr.refresh()
|
||||
completion = []
|
||||
delay = 0
|
||||
|
||||
while True:
|
||||
win_h, win_w = scr.getmaxyx()
|
||||
|
||||
for line in lines:
|
||||
line.tick(scr, steps)
|
||||
completed = line.get_completed()
|
||||
for c in completed:
|
||||
if c not in completion:
|
||||
completion.append(c)
|
||||
|
||||
if len(completion) >= len(string):
|
||||
if delay > 600:
|
||||
raise KeyboardInterrupt
|
||||
else:
|
||||
delay += 1
|
||||
|
||||
for _ in range(random_cleanup):
|
||||
x = randint(0, win_w - 1)
|
||||
y = randint(0, win_h - 1)
|
||||
|
||||
indent_len = (win_w - len(string)) / 2
|
||||
prepended_ind = (indent_len * ' ')
|
||||
|
||||
if y == win_h / 2:
|
||||
if x < len(prepended_ind):
|
||||
scr.addstr(y, x, ' ')
|
||||
if x > len(prepended_ind + string):
|
||||
scr.addstr(y, x, ' ')
|
||||
else:
|
||||
scr.addstr(y, x, ' ')
|
||||
|
||||
if window_animation is not None:
|
||||
if not window_animation.tick(scr, steps):
|
||||
window_animation = None
|
||||
|
||||
scr.refresh()
|
||||
sleep(sleep_ms)
|
||||
steps += 1
|
||||
try:
|
||||
r = rand()
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
curses.endwin()
|
||||
curses.curs_set(1)
|
||||
curses.reset_shell_mode()
|
||||
curses.echo()
|
||||
system("clear")
|
||||
|
||||
sleep(0.3)
|
||||
system("clear")
|
||||
return None
|
||||
|
||||
|
||||
def get_serial_interfaces():
|
||||
"""
|
||||
Depending on NH.py settings, determine correct serial interfaces.
|
||||
|
||||
:return: tx_if: Serial interface that connects to TxM.
|
||||
rx_if: Serial interface that connects to RxM.
|
||||
"""
|
||||
|
||||
dev_files = [df for df in listdir("/dev/")]
|
||||
dev_files.sort()
|
||||
|
||||
tx_if = ''
|
||||
rx_if = ''
|
||||
|
||||
if txm_usb_adapter and not rxm_usb_adapter:
|
||||
|
||||
adapters = []
|
||||
for dev_file in dev_files:
|
||||
if dev_file.startswith("ttyUSB"):
|
||||
adapters.append(dev_file)
|
||||
break
|
||||
|
||||
tx_if = "/dev/%s" % adapters[0]
|
||||
rx_if = "/dev/ttyS0"
|
||||
|
||||
if rxm_usb_adapter and not txm_usb_adapter:
|
||||
|
||||
adapters = []
|
||||
for dev_file in dev_files:
|
||||
if dev_file.startswith("ttyUSB"):
|
||||
adapters.append(dev_file)
|
||||
break
|
||||
|
||||
tx_if = "/dev/ttyS0"
|
||||
rx_if = "/dev/%s" % adapters[0]
|
||||
|
||||
if txm_usb_adapter and rxm_usb_adapter:
|
||||
|
||||
adapters = []
|
||||
for dev_file in dev_files:
|
||||
if dev_file.startswith("ttyUSB"):
|
||||
adapters.append(dev_file)
|
||||
|
||||
adapters.sort()
|
||||
|
||||
if not adapters:
|
||||
clean_exit("Error: No USB-serial adapters were not found.")
|
||||
|
||||
if len(adapters) < 2:
|
||||
clean_exit("Error: Settings require two USB-serial adapters.")
|
||||
|
||||
tx_if = "/dev/%s" % adapters[0]
|
||||
rx_if = "/dev/%s" % adapters[1]
|
||||
|
||||
if not txm_usb_adapter and not rxm_usb_adapter:
|
||||
|
||||
s0_found = False
|
||||
s1_found = False
|
||||
for dev_file in dev_files:
|
||||
if dev_file.startswith("ttyS0"):
|
||||
s0_found = True
|
||||
if dev_file.startswith("ttyS1"):
|
||||
s1_found = True
|
||||
|
||||
if s0_found:
|
||||
tx_if = "/dev/ttyS0"
|
||||
else:
|
||||
clean_exit("Error: /dev/ttyS0 not found.")
|
||||
|
||||
if s1_found:
|
||||
rx_if = "/dev/ttyS1"
|
||||
else:
|
||||
clean_exit("Error: /dev/ttyS1 not found.")
|
||||
|
||||
return tx_if, rx_if
|
||||
|
||||
|
||||
###############################################################################
|
||||
# RECEIVER #
|
||||
###############################################################################
|
||||
|
||||
def dbus_receiver():
|
||||
"""
|
||||
Start Qt loop that loads messages from Pidgin.
|
||||
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
DBusQtMainLoop(set_as_default=True)
|
||||
bus = SessionBus()
|
||||
bus.add_signal_receiver(pidgin_to_rxm_queue,
|
||||
dbus_interface="im.pidgin.purple.PurpleInterface",
|
||||
signal_name="ReceivedImMsg")
|
||||
|
||||
|
||||
def pidgin_to_rxm_queue(account, sender, message, conversation, flags):
|
||||
"""
|
||||
Load message from Pidgin. Put it to queue.
|
||||
|
||||
:param account: Account ID.
|
||||
:param sender: Sender account address.
|
||||
:param message: Message from sender.
|
||||
:param conversation: Conversation ID.
|
||||
:param flags: Flags.
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
# Clear PEP8 warning
|
||||
test = False
|
||||
if test:
|
||||
print(account, conversation, flags)
|
||||
|
||||
sender = sender.split('/')[0]
|
||||
tstamp = datetime.now().strftime(t_fmt)
|
||||
|
||||
if message.startswith("TFC|N|%s|P|" % int_version):
|
||||
to_rxm = str("%s|rx.%s" % (message, sender)) # Unicode to string conv.
|
||||
print("%s - pub key %s > RxM" % (tstamp, sender))
|
||||
packet_to_rxm.put(to_rxm)
|
||||
|
||||
if message.startswith("TFC|N|%s|M|" % int_version):
|
||||
to_rxm = str("%s|rx.%s" % (message, sender))
|
||||
print("%s - message %s > RxM" % (tstamp, sender))
|
||||
packet_to_rxm.put(to_rxm)
|
||||
|
||||
|
||||
def pidgin_receiver_process():
|
||||
"""
|
||||
Start QApplication as a separate process.
|
||||
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
try:
|
||||
app = QApplication(argv)
|
||||
dbus_receiver()
|
||||
app.exec_()
|
||||
except DBusException:
|
||||
pass
|
||||
|
||||
|
||||
###############################################################################
|
||||
# OTHER #
|
||||
###############################################################################
|
||||
|
||||
def header_printer_process():
|
||||
"""
|
||||
Print NH.py headers.
|
||||
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
try:
|
||||
bus = SessionBus()
|
||||
obj = bus.get_object("im.pidgin.purple.PurpleService",
|
||||
"/im/pidgin/purple/PurpleObject")
|
||||
purple = Interface(obj, "im.pidgin.purple.PurpleInterface")
|
||||
active = purple.PurpleAccountsGetAllActive()[0]
|
||||
acco_u = purple.PurpleAccountGetUsername(active)[:-1]
|
||||
|
||||
print("TFC-NaCl %s | NH.py\n" % str_version)
|
||||
print("Active account: %s\n" % acco_u)
|
||||
|
||||
except DBusException:
|
||||
raise CriticalError("header_printer_process", "DBusException. Ensure "
|
||||
"Pidgin is running.")
|
||||
|
||||
|
||||
def nh_side_command_process():
|
||||
"""
|
||||
Execute command (clear screen or exit) on NH.
|
||||
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
bus = SessionBus()
|
||||
obj = bus.get_object("im.pidgin.purple.PurpleService",
|
||||
"/im/pidgin/purple/PurpleObject")
|
||||
purple = Interface(obj, "im.pidgin.purple.PurpleInterface")
|
||||
account = purple.PurpleAccountsGetAllActive()[0]
|
||||
|
||||
while True:
|
||||
if nh_side_command.empty():
|
||||
sleep(0.001)
|
||||
continue
|
||||
|
||||
cmd = nh_side_command.get()
|
||||
|
||||
if cmd.startswith("TFC|N|%s|U|CLEAR|" % int_version):
|
||||
contact = cmd.split('|')[5]
|
||||
new_conv = purple.PurpleConversationNew(1, account, contact)
|
||||
purple.PurpleConversationClearMessageHistory(new_conv)
|
||||
system("clear")
|
||||
|
||||
|
||||
def queue_to_pidgin_process():
|
||||
"""
|
||||
Send message from queue to Pidgin.
|
||||
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
bus = SessionBus()
|
||||
obj = bus.get_object("im.pidgin.purple.PurpleService",
|
||||
"/im/pidgin/purple/PurpleObject")
|
||||
purple = Interface(obj, "im.pidgin.purple.PurpleInterface")
|
||||
account = purple.PurpleAccountsGetAllActive()[0]
|
||||
|
||||
while True:
|
||||
if message_to_pidgin.empty():
|
||||
sleep(0.001)
|
||||
continue
|
||||
|
||||
message = message_to_pidgin.get()
|
||||
|
||||
if message.startswith("TFC|N|%s|M|" % int_version):
|
||||
tfc, model, ver, pt, ct, key_id, recipient = message.split('|')
|
||||
to_pidgin = '|'.join(message.split('|')[:6])
|
||||
|
||||
if output_pidgin:
|
||||
new_conv = purple.PurpleConversationNew(1, account, recipient)
|
||||
sel_conv = purple.PurpleConvIm(new_conv)
|
||||
purple.PurpleConvImSend(sel_conv, to_pidgin)
|
||||
|
||||
if message.startswith("TFC|N|%s|P|" % int_version):
|
||||
tfc, model, ver, pt, pub_key, recipient = message.split('|')
|
||||
to_pidgin = '|'.join(message.split('|')[:5])
|
||||
|
||||
if output_pidgin:
|
||||
new_conv = purple.PurpleConversationNew(1, account, recipient)
|
||||
sel_conv = purple.PurpleConvIm(new_conv)
|
||||
purple.PurpleConvImSend(sel_conv, to_pidgin)
|
||||
|
||||
|
||||
def nh_to_rxm_sender_process():
|
||||
"""
|
||||
Send message from queue to RxM.
|
||||
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
while True:
|
||||
if packet_to_rxm.empty():
|
||||
sleep(0.001)
|
||||
continue
|
||||
|
||||
packet = packet_to_rxm.get()
|
||||
chksum = sha2_256(packet)[:checksum_len]
|
||||
packet = "%s|%s\n" % (packet, chksum)
|
||||
|
||||
if local_testing:
|
||||
ipc_rx.send(packet)
|
||||
else:
|
||||
port_to_rxm.write(packet)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# PACKETS FROM TxM #
|
||||
###############################################################################
|
||||
|
||||
def choose_txm_packet_queues(packet):
|
||||
"""
|
||||
Copy message from TxM to correct queues.
|
||||
|
||||
:param packet: Packet to copy.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if not isinstance(packet, str):
|
||||
raise FunctionParameterTypeError("choose_txm_packet_queues")
|
||||
|
||||
timestamp = datetime.now().strftime(t_fmt)
|
||||
|
||||
if packet.startswith("TFC|N|%s|M|" % int_version):
|
||||
recipient = packet.split('|')[6]
|
||||
print("%s - message TxM > %s" % (timestamp, recipient))
|
||||
to_rxm = "%s|me.%s" % ('|'.join(packet.split('|')[:6]), recipient)
|
||||
packet_to_rxm.put(to_rxm)
|
||||
message_to_pidgin.put(packet)
|
||||
|
||||
elif packet.startswith("TFC|N|%s|P|" % int_version):
|
||||
recipient = packet.split('|')[5]
|
||||
print("%s - pub key TxM > %s" % (timestamp, recipient))
|
||||
message_to_pidgin.put(packet)
|
||||
|
||||
elif packet.startswith("TFC|N|%s|C|" % int_version):
|
||||
print("%s - command TxM > RxM" % timestamp)
|
||||
packet_to_rxm.put(packet)
|
||||
|
||||
elif packet.startswith("TFC|N|%s|L|" % int_version):
|
||||
print("%s - Local key TxM > RxM" % timestamp)
|
||||
packet_to_rxm.put(packet)
|
||||
|
||||
elif packet.startswith("TFC|N|%s|U|EXIT" % int_version):
|
||||
sleep(0.5) # Time for nh_to_rxm_sender_process() to send exit-packet.
|
||||
system("clear")
|
||||
graceful_exit()
|
||||
|
||||
elif packet.startswith("TFC|N|%s|U|" % int_version):
|
||||
packet_to_rxm.put(packet)
|
||||
nh_side_command.put(packet)
|
||||
|
||||
elif packet.startswith("TFC|N|%s|I|" % int_version):
|
||||
# Skip interface configuration packet
|
||||
pass
|
||||
|
||||
else:
|
||||
print("Illegal packet from TxM:\n%s" % packet)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def txm_packet_load_process():
|
||||
"""
|
||||
Load packet from TxM via serial port (or IPC if local_testing is enabled).
|
||||
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
if local_testing:
|
||||
def ipc_to_queue(conn):
|
||||
"""
|
||||
Load packet from IPC.
|
||||
|
||||
:param conn: Listener object.
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
while True:
|
||||
sleep(0.001)
|
||||
pkg = str(conn.recv())
|
||||
|
||||
if pkg == '':
|
||||
continue
|
||||
|
||||
pkg = pkg.strip('\n')
|
||||
if not verify_checksum(pkg):
|
||||
continue
|
||||
choose_txm_packet_queues(pkg[:-9])
|
||||
|
||||
try:
|
||||
l = Listener(('', 5001))
|
||||
while True:
|
||||
ipc_to_queue(l.accept())
|
||||
except EOFError:
|
||||
system("clear")
|
||||
print("\nTxM <> NH IPC disconnected.\n")
|
||||
graceful_exit()
|
||||
|
||||
else:
|
||||
while True:
|
||||
sleep(0.001)
|
||||
packet = port_to_txm.readline()
|
||||
|
||||
if packet == '':
|
||||
continue
|
||||
|
||||
packet = packet.strip('\n')
|
||||
if not verify_checksum(packet):
|
||||
continue
|
||||
choose_txm_packet_queues(packet[:-9])
|
||||
|
||||
|
||||
def rxm_port_listener():
|
||||
"""
|
||||
Process that waits for packets from RxM interface during initial serial
|
||||
interface configuring.
|
||||
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
while True:
|
||||
data = port_to_rxm.readline()
|
||||
if data:
|
||||
configure_queue.put('FLIP')
|
||||
sleep(0.001)
|
||||
|
||||
|
||||
def txm_port_listener():
|
||||
"""
|
||||
Process that waits for packets from TxM interface during initial serial
|
||||
interface configuring.
|
||||
|
||||
:return: [no return value]
|
||||
"""
|
||||
|
||||
while True:
|
||||
data = port_to_txm.readline()
|
||||
if data:
|
||||
configure_queue.put('OK')
|
||||
sleep(0.001)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# MAIN #
|
||||
###############################################################################
|
||||
|
||||
unittesting = False # Alters function input during unittesting
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = ArgumentParser("python NH.py", usage="%(prog)s [OPTION]")
|
||||
|
||||
parser.add_argument("-p",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="quiet",
|
||||
help="do not output messages to Pidgin")
|
||||
|
||||
parser.add_argument("-l",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="local",
|
||||
help="enable local testing mode")
|
||||
|
||||
parser.add_argument("-d",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="ddsockets",
|
||||
help="enable data diode simulator sockets")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.ddsockets:
|
||||
dd_sockets = True
|
||||
|
||||
if args.quiet:
|
||||
output_pidgin = False
|
||||
|
||||
if args.local:
|
||||
local_testing = True
|
||||
|
||||
if startup_banner:
|
||||
print_banner()
|
||||
|
||||
# If local testing is disabled, initialize serial ports.
|
||||
if local_testing:
|
||||
if dd_sockets:
|
||||
rx_socket = 5002
|
||||
else:
|
||||
rx_socket = 5003
|
||||
|
||||
try:
|
||||
phase("\nWaiting for socket from Rx.py...", 35)
|
||||
ipc_rx = Client(("localhost", rx_socket))
|
||||
print("Connection established.\n")
|
||||
sleep(0.5)
|
||||
system("clear")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
clean_exit()
|
||||
|
||||
else:
|
||||
serial_tx, serial_rx = get_serial_interfaces()
|
||||
|
||||
try:
|
||||
port_to_txm = Serial(serial_tx, baud_rate, timeout=0.1)
|
||||
port_to_rxm = Serial(serial_rx, baud_rate, timeout=0.1)
|
||||
|
||||
except serialutil.SerialException:
|
||||
clean_exit("Error: Serial interfaces are set incorrectly.")
|
||||
|
||||
# Auto configure NH side serial ports
|
||||
phase("Waiting for configuration packet from TxM...", 46)
|
||||
|
||||
configure_queue = Queue()
|
||||
tl = Process(target=txm_port_listener)
|
||||
rl = Process(target=rxm_port_listener)
|
||||
tl.start()
|
||||
rl.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
sleep(0.001)
|
||||
if not configure_queue.empty():
|
||||
command = configure_queue.get()
|
||||
|
||||
if command == "FLIP":
|
||||
port_to_txm = Serial(serial_rx, baud_rate, timeout=0.1)
|
||||
port_to_rxm = Serial(serial_tx, baud_rate, timeout=0.1)
|
||||
print("Interfaces flipped.\n")
|
||||
tl.terminate()
|
||||
rl.terminate()
|
||||
break
|
||||
|
||||
if command == "OK":
|
||||
print("Interfaces OK.\n")
|
||||
tl.terminate()
|
||||
rl.terminate()
|
||||
break
|
||||
|
||||
except KeyboardInterrupt:
|
||||
tl.terminate()
|
||||
rl.terminate()
|
||||
clean_exit()
|
||||
|
||||
exit_queue = Queue()
|
||||
packet_to_rxm = Queue()
|
||||
packet_from_txm = Queue()
|
||||
nh_side_command = Queue()
|
||||
message_to_pidgin = Queue()
|
||||
message_from_pidgin = Queue()
|
||||
|
||||
hp = Process(target=header_printer_process)
|
||||
sm = Process(target=txm_packet_load_process)
|
||||
po = Process(target=queue_to_pidgin_process)
|
||||
cp = Process(target=nh_side_command_process)
|
||||
nr = Process(target=pidgin_receiver_process)
|
||||
rs = Process(target=nh_to_rxm_sender_process)
|
||||
|
||||
hp.start()
|
||||
sleep(0.5) # Allow header_printer_process() time to catch DBusException.
|
||||
sm.start()
|
||||
po.start()
|
||||
cp.start()
|
||||
nr.start()
|
||||
rs.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
if not exit_queue.empty():
|
||||
command = exit_queue.get()
|
||||
if command == "exit":
|
||||
hp.terminate()
|
||||
sm.terminate()
|
||||
po.terminate()
|
||||
cp.terminate()
|
||||
nr.terminate()
|
||||
rs.terminate()
|
||||
clean_exit()
|
||||
sleep(0.001)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
hp.terminate()
|
||||
sm.terminate()
|
||||
po.terminate()
|
||||
cp.terminate()
|
||||
nr.terminate()
|
||||
rs.terminate()
|
||||
clean_exit()
|
|
@ -0,0 +1,242 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# TFC-NaCl 0.16.01 beta || dd.py
|
||||
|
||||
"""
|
||||
GPL License
|
||||
|
||||
This software is part of the TFC application, which 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. For
|
||||
a copy of the GNU General Public License, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from os import system
|
||||
from time import sleep
|
||||
from multiprocessing.connection import Client, Listener
|
||||
from multiprocessing import Process, Queue
|
||||
from sys import argv
|
||||
|
||||
|
||||
###############################################################################
|
||||
# DATA DIODE FRAMES #
|
||||
###############################################################################
|
||||
|
||||
def lr_upper():
|
||||
print("""
|
||||
Data flow
|
||||
→
|
||||
Tx───┬─╮ ╭──────╮
|
||||
│ S > R █±6V
|
||||
GND━━━┿━┥ ├──Rx ├──GND
|
||||
│ S R █±6V
|
||||
╰─╯ ╰──────╯""")
|
||||
|
||||
|
||||
def lr_lower():
|
||||
print("""
|
||||
Data flow
|
||||
→
|
||||
Tx───┬─╮ ╭──────╮
|
||||
│ S R █±6V
|
||||
GND━━━┿━┥ ├──Rx ├──GND
|
||||
│ S > R █±6V
|
||||
╰─╯ ╰──────╯""")
|
||||
|
||||
|
||||
def lr_idle():
|
||||
print("""
|
||||
Data flow
|
||||
→
|
||||
Tx───┬─╮ ╭──────╮
|
||||
│ S R █±6V
|
||||
GND━━━┿━┥ ├──Rx ├──GND
|
||||
│ S R █±6V
|
||||
╰─╯ ╰──────╯""")
|
||||
|
||||
|
||||
def rl_upper():
|
||||
print("""
|
||||
Data flow
|
||||
←
|
||||
╭──────╮ ╭─┬───Tx
|
||||
±6V█ R < S │
|
||||
GND──┤ Rx──┤ ┝━┿━━━GND
|
||||
±6V█ R S │
|
||||
╰──────╯ ╰─╯""")
|
||||
|
||||
|
||||
def rl_lower():
|
||||
print("""
|
||||
Data flow
|
||||
←
|
||||
╭──────╮ ╭─┬───Tx
|
||||
±6V█ R S │
|
||||
GND──┤ Rx──┤ ┝━┿━━━GND
|
||||
±6V█ R < S │
|
||||
╰──────╯ ╰─╯""")
|
||||
|
||||
|
||||
def rl_idle():
|
||||
print("""
|
||||
Data flow
|
||||
←
|
||||
╭──────╮ ╭─┬───Tx
|
||||
±6V█ R S │
|
||||
GND──┤ Rx──┤ ┝━┿━━━GND
|
||||
±6V█ R S │
|
||||
╰──────╯ ╰─╯""")
|
||||
|
||||
|
||||
###############################################################################
|
||||
# DATA DIODE ANIMATORS #
|
||||
###############################################################################
|
||||
|
||||
def lr():
|
||||
for i in range(10):
|
||||
system('clear')
|
||||
lr_lower()
|
||||
sleep(0.04)
|
||||
system('clear')
|
||||
lr_upper()
|
||||
sleep(0.04)
|
||||
system("clear")
|
||||
|
||||
|
||||
def rl():
|
||||
for i in range(10):
|
||||
system('clear')
|
||||
rl_lower()
|
||||
sleep(0.04)
|
||||
system('clear')
|
||||
rl_upper()
|
||||
sleep(0.04)
|
||||
system("clear")
|
||||
|
||||
|
||||
###############################################################################
|
||||
# DATA DIODE PROCESSES #
|
||||
###############################################################################
|
||||
|
||||
def tx_process():
|
||||
|
||||
if tx_nh_lr or nh_rx_rl:
|
||||
lr_idle()
|
||||
|
||||
if tx_nh_rl or nh_rx_lr:
|
||||
rl_idle()
|
||||
|
||||
while True:
|
||||
if io_queue.empty():
|
||||
sleep(0.001)
|
||||
continue
|
||||
|
||||
msg = io_queue.get()
|
||||
|
||||
if tx_nh_lr or nh_rx_rl:
|
||||
lr()
|
||||
lr_idle()
|
||||
|
||||
if tx_nh_rl or nh_rx_lr:
|
||||
rl()
|
||||
rl_idle()
|
||||
|
||||
ipx_send.send(msg)
|
||||
|
||||
|
||||
def rx_process():
|
||||
|
||||
def ipc_to_queue(conn):
|
||||
while True:
|
||||
sleep(0.001)
|
||||
pkg = str(conn.recv())
|
||||
io_queue.put(pkg)
|
||||
try:
|
||||
l = Listener(('', input_socket))
|
||||
while True:
|
||||
ipc_to_queue(l.accept())
|
||||
except EOFError:
|
||||
exit_queue.put("exit")
|
||||
|
||||
|
||||
tx_nh_lr = False
|
||||
nh_rx_lr = False
|
||||
tx_nh_rl = False
|
||||
nh_rx_rl = False
|
||||
|
||||
input_socket = 0
|
||||
output_socket = 0
|
||||
|
||||
try:
|
||||
# Simulates data diode between Tx.py on left, NH.py on right.
|
||||
if str(argv[1]) == "txnhlr":
|
||||
tx_nh_lr = True
|
||||
input_socket = 5000
|
||||
output_socket = 5001
|
||||
|
||||
# Simulates data diode between Tx.py on right, NH.py on left.
|
||||
elif str(argv[1]) == "txnhrl":
|
||||
tx_nh_rl = True
|
||||
input_socket = 5000
|
||||
output_socket = 5001
|
||||
|
||||
# Simulates data diode between Rx.py on left, NH.py on right.
|
||||
elif str(argv[1]) == "nhrxlr":
|
||||
nh_rx_lr = True
|
||||
input_socket = 5002
|
||||
output_socket = 5003
|
||||
|
||||
# Simulates data diode between Rx.py on right, NH.py on left.
|
||||
elif str(argv[1]) == "nhrxrl":
|
||||
nh_rx_rl = True
|
||||
input_socket = 5002
|
||||
output_socket = 5003
|
||||
|
||||
else:
|
||||
system("clear")
|
||||
print "\nUsage: python dd.py {txnh{lr,rl}, nhrx{lr,rl}\n"
|
||||
exit()
|
||||
|
||||
except IndexError:
|
||||
system("clear")
|
||||
print "\nUsage: python dd.py {txnh{lr,rl}, nhrx{lr,rl}}\n"
|
||||
exit()
|
||||
|
||||
try:
|
||||
print('Waiting for socket"')
|
||||
ipx_send = Client(("localhost", output_socket))
|
||||
print("Connection established.")
|
||||
sleep(0.3)
|
||||
system("clear")
|
||||
except KeyboardInterrupt:
|
||||
exit()
|
||||
|
||||
exit_queue = Queue()
|
||||
io_queue = Queue()
|
||||
|
||||
txp = Process(target=tx_process)
|
||||
rxp = Process(target=rx_process)
|
||||
|
||||
txp.start()
|
||||
rxp.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
if not exit_queue.empty():
|
||||
command = exit_queue.get()
|
||||
if command == "exit":
|
||||
txp.terminate()
|
||||
rxp.terminate()
|
||||
exit()
|
||||
sleep(0.01)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
txp.terminate()
|
||||
rxp.terminate()
|
||||
exit()
|
|
@ -0,0 +1,165 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# TFC-NaCl 0.16.01 beta || hwrng.py
|
||||
|
||||
"""
|
||||
GPL License
|
||||
|
||||
This software is part of the TFC application, which 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. For
|
||||
a copy of the GNU General Public License, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from binascii import hexlify
|
||||
from os import system
|
||||
from time import sleep
|
||||
from hashlib import sha256
|
||||
from simplesha3 import sha3256
|
||||
|
||||
try:
|
||||
import RPi.GPIO as GPIO
|
||||
except ImportError:
|
||||
GPIO = None
|
||||
pass
|
||||
|
||||
str_version = "0.16.01 beta"
|
||||
int_version = 1601
|
||||
|
||||
|
||||
###############################################################################
|
||||
# ERROR CLASSES #
|
||||
###############################################################################
|
||||
|
||||
class FunctionParameterTypeError(Exception):
|
||||
"""
|
||||
hwrng.py should gracefully exit if function is called with incorrect
|
||||
parameter types.
|
||||
"""
|
||||
|
||||
def __init__(self, function_name):
|
||||
system("clear")
|
||||
print("\nError: M(%s): Wrong input type.\n" % function_name)
|
||||
exit()
|
||||
|
||||
|
||||
###############################################################################
|
||||
# CRYPTOGRAPHY #
|
||||
###############################################################################
|
||||
|
||||
def sha2_256(message):
|
||||
"""
|
||||
Generate SHA256 digest from message.
|
||||
|
||||
:param message: Input to hash function.
|
||||
:return: Hex representation of SHA256 digest.
|
||||
"""
|
||||
|
||||
if not isinstance(message, str):
|
||||
raise FunctionParameterTypeError("sha2_256")
|
||||
|
||||
h_function = sha256()
|
||||
h_function.update(message)
|
||||
hex_digest = hexlify(h_function.digest())
|
||||
|
||||
return hex_digest
|
||||
|
||||
|
||||
def sha3_256(message):
|
||||
"""
|
||||
Generate SHA3-256 digest from message.
|
||||
|
||||
:param message: Input to hash function.
|
||||
:return: Hex representation of SHA3-256 digest.
|
||||
"""
|
||||
|
||||
if not isinstance(message, str):
|
||||
raise FunctionParameterTypeError("sha3_256")
|
||||
|
||||
return hexlify(sha3256(message))
|
||||
|
||||
|
||||
def get_hwrng_entropy():
|
||||
"""
|
||||
Get HWRNG entropy.
|
||||
|
||||
Load entropy from HWRNG through GPIO pins.
|
||||
|
||||
Before sampling starts, a loop collects 3000 samples to allow the HWRNG
|
||||
time to warm. Sampling is done at 10Hz frequency to ensure minimal
|
||||
auto-correlation between samples. Entropy is compressed with SHA3-256
|
||||
before it is returned.
|
||||
|
||||
:return: 32 bytes of entropy.
|
||||
"""
|
||||
|
||||
def digits_to_bytes(di):
|
||||
"""
|
||||
Convert string of binary digits to byte string.
|
||||
|
||||
:param di: Digit string.
|
||||
:return: Byte string.
|
||||
"""
|
||||
|
||||
return ''.join(chr(int(di[i:i + 8], 2)) for i in xrange(0, len(di), 8))
|
||||
|
||||
# Sampling settings
|
||||
sample_delay = 0.1
|
||||
gpio_port = 4
|
||||
samples_n = 256
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setup(gpio_port, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||||
|
||||
try:
|
||||
warmup_zero = 0
|
||||
warmup_one = 0
|
||||
|
||||
while True:
|
||||
if warmup_one > 1500 and warmup_zero > 1500:
|
||||
break
|
||||
if GPIO.input(gpio_port) == 1:
|
||||
warmup_one += 1
|
||||
else:
|
||||
warmup_zero += 1
|
||||
sleep(0.001)
|
||||
|
||||
# Perform Von Neumann whitening during sampling
|
||||
vn_digits = ''
|
||||
|
||||
while True:
|
||||
|
||||
if len(vn_digits) >= samples_n:
|
||||
break
|
||||
|
||||
first_bit = GPIO.input(gpio_port)
|
||||
sleep(sample_delay)
|
||||
|
||||
second_bit = GPIO.input(gpio_port)
|
||||
sleep(sample_delay)
|
||||
|
||||
if first_bit == second_bit:
|
||||
continue
|
||||
else:
|
||||
vn_digits += str(first_bit)
|
||||
|
||||
hwrng_bytes = digits_to_bytes(vn_digits)
|
||||
|
||||
if len(hwrng_bytes) != 32:
|
||||
print("ERROR")
|
||||
|
||||
entropy = sha3_256(hwrng_bytes)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
GPIO.cleanup()
|
||||
raise
|
||||
|
||||
GPIO.cleanup()
|
||||
print entropy
|
||||
|
||||
get_hwrng_entropy()
|
|
@ -0,0 +1,97 @@
|
|||
<img align="right" src="https://cs.helsinki.fi/u/oottela/tfclogo.png" style="position: relative; top: 0; left: 0;">
|
||||
|
||||
|
||||
###Tinfoil Chat NaCl
|
||||
|
||||
|
||||
TFC-NaCl is a high assurance encryption plugin for Pidgin IM client that
|
||||
protects users from [passive eavesdropping](https://en.wikipedia.org/wiki/Upstream_collection),
|
||||
[active MITM attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack)
|
||||
and remote CNE (aka equipment interference) of endpoints practiced by TLAs such
|
||||
as the [NSA](https://firstlook.org/theintercept/2014/03/12/nsa-plans-infect-millions-computers-malware/),
|
||||
[GCHQ](http://www.wired.co.uk/news/archive/2014-03/13/nsa-turbine) and
|
||||
[BKA](http://ccc.de/en/updates/2011/staatstrojaner).
|
||||
|
||||
|
||||
###Overview
|
||||
|
||||
**High end end-to-end encryption** with Curve25519 ECDHE / PSKs.
|
||||
XSalsa20-Poly1305 AEAD with forward secrecy and deniability.
|
||||
|
||||
|
||||
**High entropy key generation** with /dev/urandom mixed with entropy from
|
||||
open circuit design GPIO HWRNG of RPi that acts either as TCB itself or as
|
||||
a sampling device over SSH.
|
||||
|
||||
|
||||
**Strong endpoint security** is obtained with hardware-based TCB separation:
|
||||
Unidirectional data flow between end point's three computers prevents
|
||||
either injection of malware or exfiltration of keys and plaintexts from TCB
|
||||
regardless of existing software zero-day vulnerabilities in software.
|
||||
|
||||
|
||||
**Defeats metadata** about quantity and schedule of communication with
|
||||
trickle connection that outputs constant stream of encrypted noise data.
|
||||
|
||||
|
||||
**Covert file transfer** that takes place in background during
|
||||
trickle connection.
|
||||
|
||||
|
||||
**Multicasting** of messages and files to multiple recipients (no trickle
|
||||
supported).
|
||||
|
||||
|
||||
###How it works
|
||||
|
||||
![](https://cs.helsinki.fi/u/oottela/tfc_graph2.png)
|
||||
|
||||
TFC uses three computers per endpoint. Alice enters her commands and messages to
|
||||
program Tx.py running on her Transmitter computer (TxM), a [TCB](https://en.wikipedia.org/wiki/Trusted_computing_base)
|
||||
separated from network. Tx.py encrypts and signs plaintext data and relays it
|
||||
to receiver computers (RxM) via networked computer (NH) through RS-232 interface
|
||||
and a data diode.
|
||||
|
||||
Depending on packet type, the program NH.py running on Alice's NH forwards
|
||||
packets from TxM's serial interface to Pidgin and local RxM (through another
|
||||
RS-232 interface and data diode). Local RxM authenticates and decrypts received
|
||||
data before processing it.
|
||||
|
||||
Pidgin sends the packet either directly or through Tor network to IM server,
|
||||
that then forwards it directly (or again through Tor) to Bob.
|
||||
|
||||
NH.py on Bob's NH receives Alice's packet from Pidgin, and forwards it through
|
||||
RS-232 interface and data diode to Bob's RxM, where the ciphertext is
|
||||
authenticated, decrypted, displayed and optionally also logged. When the Bob
|
||||
responds, he will send the message using his TxM and in the end Alice reads the
|
||||
message from her RxM.
|
||||
|
||||
|
||||
###Why keys can not be exfiltrated
|
||||
|
||||
1. Malware that exploits an unknown vulnerability in RxM can infiltrate to
|
||||
system, but is unable to exfiltrate keys or plaintexts, as data diode prevents
|
||||
all outbound traffic.
|
||||
|
||||
2. Malware can not breach TxM as data diode prevents all inbound traffic. The
|
||||
only data input from RxM to TxM is the 72 char public key, manually typed by
|
||||
user.
|
||||
|
||||
3. The NH is assumed to be compromised, but unencrypted data never touches it.
|
||||
|
||||
![](https://cs.helsinki.fi/u/oottela/tfc_attacks2.png)
|
||||
|
||||
The optical gap of the data diode (below) physically blocks back channels.
|
||||
|
||||
<img src="https://cs.helsinki.fi/u/oottela/data_diode.png" align="center" width="74%" height="74%"/>
|
||||
|
||||
|
||||
###More information
|
||||
|
||||
White paper and manual for previous versions are listed below. TFC-NaCl specific
|
||||
updates are listed in the updatelog. Updated white paper and documentation are
|
||||
under work.
|
||||
|
||||
White paper: https://cs.helsinki.fi/u/oottela/tfc.pdf
|
||||
|
||||
Manual: https://cs.helsinki.fi/u/oottela/tfc-manual.pdf
|
|
@ -0,0 +1,805 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# TFC-NaCl 0.16.01 beta || setup.py
|
||||
|
||||
"""
|
||||
GPL License
|
||||
|
||||
This software is part of the TFC application, which 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. For
|
||||
a copy of the GNU General Public License, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from os import chdir, getcwd, system, geteuid
|
||||
from subprocess import Popen, check_output
|
||||
|
||||
str_version = "0.16.01 beta"
|
||||
int_version = 1601
|
||||
|
||||
repository = "https://raw.githubusercontent.com/maqp/tfc-nacl/master/"
|
||||
|
||||
|
||||
###############################################################################
|
||||
# APT COMMANDS #
|
||||
###############################################################################
|
||||
|
||||
sagi = "sudo apt-get --yes install"
|
||||
|
||||
|
||||
def cmd(message, command):
|
||||
"""
|
||||
Display message and run command as subprocess.
|
||||
|
||||
:param message: Message to be displayed.
|
||||
:param command: Command to run.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
print("\n%s\n" % message)
|
||||
Popen(command, shell=True).wait()
|
||||
return None
|
||||
|
||||
|
||||
def update_repositories():
|
||||
cmd("Updating repository list", "sudo apt-get --yes update")
|
||||
|
||||
|
||||
def install_python_serial():
|
||||
cmd("Installing Python serial", "%s python-serial" % sagi)
|
||||
|
||||
|
||||
def install_python_qt4():
|
||||
cmd("Installing Python QT4", "%s python-qt4" % sagi)
|
||||
|
||||
|
||||
def install_python_qt4_dbus():
|
||||
cmd("Installing Python QT4-DBus", "%s python-qt4-dbus" % sagi)
|
||||
|
||||
|
||||
def install_pidgin():
|
||||
cmd("Installing Pidgin", "%s pidgin" % sagi)
|
||||
|
||||
|
||||
def install_pidgin_otr():
|
||||
cmd("Installing Pidgin OTR", "%s pidgin-otr" % sagi)
|
||||
|
||||
|
||||
def install_python_pip():
|
||||
cmd("Installing Python pip", "%s python-pip" % sagi)
|
||||
|
||||
|
||||
def install_python_setuptools():
|
||||
cmd("Installing Python Setuptools", "%s python-setuptools" % sagi)
|
||||
|
||||
|
||||
def install_python_dev():
|
||||
cmd("Installing Python-Dev", "%s python-dev" % sagi)
|
||||
|
||||
|
||||
def install_libffi_dev():
|
||||
cmd("Installing libffi", "%s libffi-dev" % sagi)
|
||||
|
||||
|
||||
def install_tkinter():
|
||||
cmd("Installing Python Tkinter", "%s python-tk" % sagi)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# PIP COMMANDS #
|
||||
###############################################################################
|
||||
|
||||
def pip_passlib():
|
||||
cmd("Installing PassLib", "sudo pip install passlib")
|
||||
|
||||
|
||||
def pip_simplesha3():
|
||||
cmd("Installing SimpleSHA3", " sudo pip install simplesha3")
|
||||
|
||||
|
||||
def pip_paramiko():
|
||||
cmd("Installing SimpleSHA3", " sudo pip install paramiko")
|
||||
|
||||
|
||||
###############################################################################
|
||||
# FILE HASH VERIFICATION #
|
||||
###############################################################################
|
||||
|
||||
hash_list = ("""
|
||||
b7093331d65eaf36b218f105a0b7a710ade5cf520b96da4f4b580600adf4ad71 tfc-mods.zip
|
||||
c76c6ec69dc7b233dc58aff42b45d87e8ec8f8e4e32ecf790260ca9e436ec42f dd.py
|
||||
264889d2d2f06d258035bec8baa2e1300f42805d2d6b1987faf1c5cb6e72bbf7 hwrng.py
|
||||
142cb4cb8ba803880015ccd3a82d5428a4480f4e7e463245977ba797ebf72565 NH.py
|
||||
d101d5a1308a09a79824d2652dcad0319c504e50a71d96fee9d3d189df50a946 Rx.py
|
||||
af392c14f4390103ab9f28d042b8dc353b1d9f5590aff01d9a456047c6babd4d test_nh.py
|
||||
acd7ca80c2b7b3f15c5a588d0dfc858246f1675456d787d655e7af093d5ab2ed test_rx.py
|
||||
73a01d9176f883e16b48858ee62ae847f09b9ab0e663db5a4f8628081638a0b9 test_tx.py
|
||||
1553911611f2ebdd54c55d8c2d9ef822378169a0c3ea8e66495c2d6ae73346d5 Tx.py
|
||||
""")
|
||||
|
||||
|
||||
def check_file_hash(filename):
|
||||
"""
|
||||
Verify that SHA-256 hash of file matches the one in installer.
|
||||
|
||||
WARNING!
|
||||
|
||||
Downloading the installer through TLS-encrypted GitHub website is *NOT* a
|
||||
guarantee that a state adversary could not edit the downloaded source code
|
||||
on the fly with great ease.
|
||||
|
||||
Unless you can verify the origin of *this* installer, you can NOT trust the
|
||||
hashes written above: in other words, the hashes only protect against
|
||||
unintentional transmission errors, NOT against malicious actor. You must
|
||||
obtain the GPG signing key to verify the authenticity of this installer.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
No TLS-MITM attack free way exists to obtain the signing key online.
|
||||
--------------------------------------------------------------------
|
||||
|
||||
The only reasonably secure way to obtain the signing key is personal
|
||||
handout of key or GPG Web of Trust with respected and trustworthy members.
|
||||
|
||||
:param filename: File to verify.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
f_hash = check_output(["sha256sum", filename]).split()[0]
|
||||
|
||||
h_list = hash_list.split('\n')
|
||||
for h in h_list:
|
||||
if filename in h:
|
||||
if f_hash not in h:
|
||||
system("clear")
|
||||
print("CRITICAL ERROR: SHA2-256 hash of %s was incorrect. \n"
|
||||
"This might indicate a TLS-MITM attack, transmission\n"
|
||||
"error or that this installer is outdated.\n") % filename
|
||||
exit()
|
||||
|
||||
print("\nSHA256 hash of %s was correct.\n" % filename)
|
||||
return None
|
||||
|
||||
|
||||
###############################################################################
|
||||
# CRYPTO LIBRARIES COMMANDS #
|
||||
###############################################################################
|
||||
|
||||
def pynacl_install():
|
||||
"""
|
||||
Install the PyNaCl library.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
app_root_directory = getcwd()
|
||||
|
||||
cmd("Downloading PyNaCl Library", "wget https://github.com/maqp/"
|
||||
"pynacl/archive/tfc-mods.zip")
|
||||
|
||||
check_file_hash("tfc-mods.zip")
|
||||
|
||||
cmd("Unzipping PyNaCl Library", "unzip tfc-mods.zip")
|
||||
|
||||
chdir("pynacl-tfc-mods/")
|
||||
|
||||
cmd("Installing PyNaCl Library", "sudo python setup.py install")
|
||||
|
||||
chdir(app_root_directory)
|
||||
|
||||
cmd("Removing PyNaCl (tfc-mods.zip)", "rm tfc-mods.zip")
|
||||
|
||||
Popen("sudo rm -r pynacl-master/", shell=True).wait()
|
||||
|
||||
system("clear")
|
||||
|
||||
if not yes("\n Keep PyNaCl source files?\n"):
|
||||
Popen("sudo rm -rf pynacl-tfc-mods", shell=True).wait()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
###############################################################################
|
||||
# DOWNLOAD TFC PROGRAMS #
|
||||
###############################################################################
|
||||
|
||||
def get_tx():
|
||||
cmd("Downloading Tx.py (TxM)", "wget %sTx.py" % repository)
|
||||
check_file_hash("Tx.py")
|
||||
|
||||
cmd("Downloading test_tx.py (TxM)", "wget %sunittests/test_tx.py"
|
||||
% repository)
|
||||
check_file_hash("test_tx.py")
|
||||
|
||||
|
||||
def get_hwrng():
|
||||
cmd("Downloading hwrng.py", "wget %shwrng.py" % repository)
|
||||
check_file_hash("hwrng.py")
|
||||
|
||||
|
||||
def get_rx():
|
||||
cmd("Downloading Rx.py (RxM)", "wget %sRx.py" % repository)
|
||||
check_file_hash("Rx.py")
|
||||
|
||||
cmd("Downloading test_rx.py (RxM)", "wget %sunittests/test_rx.py"
|
||||
% repository)
|
||||
check_file_hash("test_rx.py")
|
||||
|
||||
|
||||
def get_nh():
|
||||
cmd("Downloading NH.py (NH)", "wget %sNH.py" % repository)
|
||||
check_file_hash("NH.py")
|
||||
|
||||
cmd("Downloading test_nh.py (NH)", "wget %sunittests/test_nh.py"
|
||||
% repository)
|
||||
check_file_hash("test_nh.py")
|
||||
|
||||
|
||||
def get_dd():
|
||||
cmd("Downloading dd.py (NH)", "wget %sdd.py" % repository)
|
||||
check_file_hash("dd.py")
|
||||
|
||||
|
||||
###############################################################################
|
||||
# EDIT TFC PROGRAMS #
|
||||
###############################################################################
|
||||
|
||||
def rasp_disable_boot_info():
|
||||
"""
|
||||
Disable Boot info through serial port to keep NH's TxM serial interface's
|
||||
input only related to Tx.py output.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
print("\nEditing file 'cmdline.txt'.\n")
|
||||
|
||||
try:
|
||||
content = open('/boot/cmdline.txt').readline()
|
||||
content = content.replace(" console=ttyAMA0,115200", '')
|
||||
open('/boot/cmdline.txt', 'w+').write(content)
|
||||
|
||||
except IOError:
|
||||
print("CRITICAL ERROR! M(rasp_disable_boot_info):\n"
|
||||
"/boot/cmdline.txt could not be accessed.\n"
|
||||
"Exiting setup.py")
|
||||
exit()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def ssh_hwrng_connection():
|
||||
"""
|
||||
Ask user whether they will use Raspbian over SSH to load entropy. If yes,
|
||||
enable SSH client for Tx.py.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if not yes("Will TxM load entropy from Raspberry Pi over SSH?"):
|
||||
return None
|
||||
|
||||
print("\nEnabling SSH client during key generation\n")
|
||||
contents = open("Tx.py").read()
|
||||
contents = contents.replace("use_ssh_hwrng = False",
|
||||
"use_ssh_hwrng = True")
|
||||
open("Tx.py", "w+").write(contents)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def serial_config_raspbian(program):
|
||||
"""
|
||||
Ask user whether they will use USB serial interface.
|
||||
Answering no enables Raspberry Pi's integrated interface /dev/ttyAMA0.
|
||||
|
||||
:param program: Program the serial interface of which is changed.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if program == "Tx.py":
|
||||
if yes("Will TxM connect to NH using USB to serial adapter?"):
|
||||
return None
|
||||
|
||||
if program == "Rx.py":
|
||||
if yes("Will RxM connect to NH using USB to serial adapter?"):
|
||||
return None
|
||||
|
||||
print("\nChanging %s's NH serial-interface to integrated.\n" % program)
|
||||
contents = open(program).read()
|
||||
contents = contents.replace("nh_usb_adapter = True",
|
||||
"nh_usb_adapter = False")
|
||||
open(program, "w+").write(contents)
|
||||
return None
|
||||
|
||||
|
||||
def serial_config_integrated(program):
|
||||
"""
|
||||
Ask user whether they will use USB to serial adapter.
|
||||
Answering no enables integrated interface /dev/ttyS0.
|
||||
|
||||
:param program: Program the serial interface of which is changed.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if program == "NH.py":
|
||||
if yes("Will NH connect to TxM using USB to serial adapter?"):
|
||||
pass
|
||||
else:
|
||||
print("\nChanging NH.py's TxM serial-interface to integrated.\n")
|
||||
contents = open(program).read()
|
||||
contents = contents.replace("txm_usb_adapter = True",
|
||||
"txm_usb_adapter = False")
|
||||
open(program, "w+").write(contents)
|
||||
|
||||
if yes("Will NH connect to RxM using USB to serial adapter?"):
|
||||
pass
|
||||
else:
|
||||
print("\nChanging NH.py's RxM serial-interface to integrated.\n")
|
||||
contents = open(program).read()
|
||||
contents = contents.replace("rxm_usb_adapter = True",
|
||||
"rxm_usb_adapter = False")
|
||||
open(program, "w+").write(contents)
|
||||
return None
|
||||
|
||||
if program == "Tx.py":
|
||||
if yes("Will TxM connect to NH using USB to serial adapter?"):
|
||||
return None
|
||||
|
||||
if program == "Rx.py":
|
||||
if yes("Will RxM connect to NH using USB to serial adapter?"):
|
||||
return None
|
||||
|
||||
print("\nChanging %s's NH serial-interface to integrated.\n" % program)
|
||||
contents = open(program).read()
|
||||
contents = contents.replace("nh_usb_adapter = True",
|
||||
"nh_usb_adapter = False")
|
||||
open(program, "w+").write(contents)
|
||||
return None
|
||||
|
||||
|
||||
def change_to_local(file_name):
|
||||
"""
|
||||
Configure {Tx,Rx,NH}.py local_testing boolean to True.
|
||||
|
||||
:param file_name: Target program from which local_testing is enabled.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
print("\nEnabling 'local_testing' boolean in program '%s'.\n" % file_name)
|
||||
content = open(file_name).read()
|
||||
content = content.replace("local_testing = False",
|
||||
"local_testing = True")
|
||||
open(file_name, "w+").write(content)
|
||||
return None
|
||||
|
||||
|
||||
###############################################################################
|
||||
# MISC #
|
||||
###############################################################################
|
||||
|
||||
def set_serial_permissions(username=''):
|
||||
"""
|
||||
Add username to 'dialout' group to allow operation
|
||||
of serial port without root privileges.
|
||||
|
||||
:param username: Username to be added.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if username == '':
|
||||
while True:
|
||||
print("\nType name of the user that will be running TFC to add\n"
|
||||
"them to dialout group, that enables serial interfaces.")
|
||||
username = raw_input("\n >")
|
||||
if yes("\n Confirm user '%s'?" % username):
|
||||
break
|
||||
cmd('', "sudo gpasswd --add %s dialout" % username)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def yes(prompt):
|
||||
"""
|
||||
Prompt user a question that is answered with yes / no.
|
||||
|
||||
:param prompt: Question to be asked.
|
||||
:return: True if user types 'y' or 'yes', otherwise returns False.
|
||||
"""
|
||||
|
||||
while True:
|
||||
try:
|
||||
answer = raw_input("%s (y/n): " % prompt)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
|
||||
if answer.lower() in ("yes", 'y'):
|
||||
return True
|
||||
|
||||
elif answer.lower() in ("no", 'n'):
|
||||
return False
|
||||
|
||||
|
||||
def print_menu():
|
||||
"""
|
||||
Display the menu with list of installation configurations.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
print("TFC-NaCl %s || setup.py" % str_version)
|
||||
print("""
|
||||
Select a device-OS configuration (tested distros are listed):
|
||||
|
||||
TxM
|
||||
1. Raspbian Jessie (Run this installer as sudo)
|
||||
|
||||
2. Ubuntu 14.04 LTS
|
||||
Kubuntu 14.04 LTS
|
||||
Xubuntu 14.04 LTS
|
||||
Lubuntu 15.04
|
||||
Linux Mint 17.3 Rosa
|
||||
|
||||
HWRNG (over SSH from TxM)
|
||||
3. Raspbian Jessie (Run this installer as sudo)
|
||||
|
||||
RxM
|
||||
4. Raspbian Jessie (Run this installer as sudo)
|
||||
|
||||
5. Ubuntu 14.04 LTS
|
||||
Kubuntu 14.04 LTS
|
||||
Xubuntu 14.04 LTS
|
||||
Lubuntu 15.04
|
||||
Linux Mint 17.3 Rosa
|
||||
|
||||
NH
|
||||
6. Ubuntu 14.04 LTS
|
||||
Kubuntu 14.04 LTS
|
||||
Xubuntu 14.04 LTS
|
||||
Lubuntu 15.04
|
||||
Linux Mint 17.3 Rosa
|
||||
|
||||
7. Tails 2.0
|
||||
|
||||
Local Testing (insecure)
|
||||
8. Ubuntu 14.04 LTS
|
||||
Kubuntu 14.04 LTS
|
||||
Xubuntu 14.04 LTS
|
||||
Lubuntu 15.04
|
||||
Linux Mint 17.3 Rosa\n""")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def print_local_tester_warning():
|
||||
"""
|
||||
Display a warning about insecurity of local testing.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
print("\n WARNING! \n"
|
||||
" YOU HAVE SELECTED THE LOCAL TESTING CONFIGURATION FOR TFC. \n"
|
||||
" THIS VERSION IS INTENDED ONLY FOR TRYING OUT THE FEATURES AND \n"
|
||||
" STABILITY OF THE SYSTEM. IN THIS CONFIGURATION, THE ENCRYPTION\n"
|
||||
" KEYS ARE GENERATED, STORED AND HANDLED ON NETWORK-CONNECTED \n"
|
||||
" COMPUTER, SO ANYONE WHO BREAKS IN TO IT BY EXPLOITING A KNOWN \n"
|
||||
" (OR UNKNOWN ZERO DAY) VULNERABILITY, CAN DECRYPT AND/OR FORGE \n"
|
||||
" ALL MESSAGES YOU SEND AND RECEIVE!")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def print_local_test_install_finish():
|
||||
|
||||
print("\n Test folder 'tfc-nacl' has been generated. Initiate \n"
|
||||
" OTR-encrypted Pidgin conversation and run Tx.py, Rx.py\n"
|
||||
" and NH.py in their own terminals.\n")
|
||||
|
||||
|
||||
###############################################################################
|
||||
# INSTALL ROUTINES #
|
||||
###############################################################################
|
||||
|
||||
def raspbian_txm():
|
||||
if yes("Install TxM configuration for Raspbian Jessie?"):
|
||||
|
||||
if geteuid() != 0:
|
||||
print("\nError: Raspbian installer requires root privileges.\n"
|
||||
"\nExiting.\n")
|
||||
exit()
|
||||
|
||||
update_repositories()
|
||||
|
||||
install_python_setuptools()
|
||||
install_python_dev()
|
||||
install_libffi_dev()
|
||||
|
||||
install_python_pip()
|
||||
pip_passlib()
|
||||
pip_simplesha3()
|
||||
pynacl_install()
|
||||
|
||||
rasp_disable_boot_info()
|
||||
|
||||
Popen("mkdir tfc-nacl", shell=True).wait()
|
||||
chdir("tfc-nacl/")
|
||||
|
||||
get_tx()
|
||||
serial_config_raspbian("Tx.py")
|
||||
set_serial_permissions()
|
||||
|
||||
system("clear")
|
||||
print("\nTxM side installation complete.\n"
|
||||
"Reboot the system before running.\n")
|
||||
exit()
|
||||
|
||||
|
||||
def raspbian_hwrng():
|
||||
if yes("Install HWRNG configuration for Raspbian Jessie?"):
|
||||
|
||||
if geteuid() != 0:
|
||||
print("\nError: Raspbian installer requires root privileges.\n"
|
||||
"\nExiting.\n")
|
||||
exit()
|
||||
|
||||
update_repositories()
|
||||
|
||||
install_python_setuptools()
|
||||
install_python_dev()
|
||||
install_libffi_dev()
|
||||
|
||||
install_python_pip()
|
||||
pip_simplesha3()
|
||||
|
||||
get_hwrng()
|
||||
|
||||
system("clear")
|
||||
print("\nHWRNG side installation complete.\n"
|
||||
"Reboot the system before running.\n")
|
||||
exit()
|
||||
|
||||
|
||||
def ubuntu_txm():
|
||||
if yes("Install TxM configuration for *buntu / Linux Mint?"):
|
||||
|
||||
update_repositories()
|
||||
install_python_serial()
|
||||
install_tkinter()
|
||||
|
||||
install_python_setuptools()
|
||||
install_python_dev()
|
||||
install_libffi_dev()
|
||||
|
||||
install_python_pip()
|
||||
pip_passlib()
|
||||
pip_simplesha3()
|
||||
pip_paramiko()
|
||||
pynacl_install()
|
||||
|
||||
Popen("mkdir tfc-nacl", shell=True).wait()
|
||||
chdir("tfc-nacl/")
|
||||
|
||||
get_tx()
|
||||
serial_config_integrated("Tx.py")
|
||||
ssh_hwrng_connection()
|
||||
set_serial_permissions()
|
||||
|
||||
system("clear")
|
||||
print("\nTxM side installation complete.\n"
|
||||
"Reboot the system before running.\n")
|
||||
exit()
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def raspbian_rxm():
|
||||
if yes("Install RxM configuration for Raspbian Jessie?"):
|
||||
|
||||
if geteuid() != 0:
|
||||
print("\nError: Raspbian installer requires root privileges.\n"
|
||||
"\nExiting.\n")
|
||||
exit()
|
||||
|
||||
update_repositories()
|
||||
|
||||
install_python_setuptools()
|
||||
install_python_dev()
|
||||
install_libffi_dev()
|
||||
|
||||
install_python_pip()
|
||||
pip_passlib()
|
||||
pip_simplesha3()
|
||||
pynacl_install()
|
||||
|
||||
rasp_disable_boot_info()
|
||||
|
||||
Popen("mkdir tfc-nacl", shell=True).wait()
|
||||
chdir("tfc-nacl/")
|
||||
|
||||
get_rx()
|
||||
serial_config_raspbian("Rx.py")
|
||||
set_serial_permissions()
|
||||
|
||||
system("clear")
|
||||
print("\nRxM side installation complete.\n"
|
||||
"Reboot the system before running.\n")
|
||||
exit()
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def ubuntu_rxm():
|
||||
if yes("Install RxM configuration for *buntu / Linux Mint?"):
|
||||
|
||||
update_repositories()
|
||||
install_python_serial()
|
||||
|
||||
install_python_setuptools()
|
||||
install_python_dev()
|
||||
install_libffi_dev()
|
||||
|
||||
install_python_pip()
|
||||
pip_passlib()
|
||||
pip_simplesha3()
|
||||
pynacl_install()
|
||||
|
||||
Popen("mkdir tfc-nacl", shell=True).wait()
|
||||
chdir("tfc-nacl/")
|
||||
|
||||
get_rx()
|
||||
serial_config_integrated("Rx.py")
|
||||
set_serial_permissions()
|
||||
|
||||
system("clear")
|
||||
print("\nRxM side installation complete.\n"
|
||||
"Reboot the system before running.\n")
|
||||
exit()
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def ubuntu_nh():
|
||||
if yes("Install NH configuration for *buntu / Linux Mint?"):
|
||||
|
||||
update_repositories()
|
||||
install_python_qt4()
|
||||
install_python_qt4_dbus()
|
||||
install_python_serial()
|
||||
|
||||
if yes("\nInstall Pidgin with OTR-plugin?"):
|
||||
install_pidgin()
|
||||
install_pidgin_otr()
|
||||
|
||||
get_nh()
|
||||
serial_config_integrated("NH.py")
|
||||
set_serial_permissions()
|
||||
|
||||
system("clear")
|
||||
print("\nNH side installation complete.\n"
|
||||
"Reboot the system before running.\n")
|
||||
exit()
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def tails_nh():
|
||||
if yes("Install NH configuration for Tails LiveCD / LiveUSB?"):
|
||||
|
||||
update_repositories()
|
||||
install_python_serial()
|
||||
install_python_qt4_dbus()
|
||||
|
||||
set_serial_permissions("amnesia")
|
||||
get_nh()
|
||||
serial_config_integrated("NH.py")
|
||||
|
||||
system("clear")
|
||||
print("\nNH install script completed. Initiate OTR-encrypted\n"
|
||||
"Pidgin conversation and launch NH.py.\n\nExiting.\n")
|
||||
exit()
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def local_testing():
|
||||
system("clear")
|
||||
print_local_tester_warning()
|
||||
|
||||
if not raw_input("\n TYPE 'INSECURE' TO VERIFY "
|
||||
"YOU UNDERSTAND THE RISKS: ") == "INSECURE":
|
||||
return None
|
||||
|
||||
system("clear")
|
||||
|
||||
update_repositories()
|
||||
|
||||
install_python_qt4()
|
||||
install_python_qt4_dbus()
|
||||
install_python_serial()
|
||||
install_tkinter()
|
||||
|
||||
install_python_setuptools()
|
||||
install_python_dev()
|
||||
install_libffi_dev()
|
||||
|
||||
install_python_pip()
|
||||
pip_passlib()
|
||||
pip_simplesha3()
|
||||
pip_paramiko()
|
||||
pynacl_install()
|
||||
|
||||
if yes("\n Install Pidgin with OTR-plugin?"):
|
||||
install_pidgin()
|
||||
install_pidgin_otr()
|
||||
|
||||
Popen("mkdir tfc-nacl", shell=True).wait()
|
||||
chdir("tfc-nacl/")
|
||||
|
||||
get_tx()
|
||||
get_nh()
|
||||
get_rx()
|
||||
get_dd()
|
||||
|
||||
change_to_local("Tx.py")
|
||||
change_to_local("Rx.py")
|
||||
change_to_local("NH.py")
|
||||
ssh_hwrng_connection()
|
||||
|
||||
system("clear")
|
||||
print_local_test_install_finish()
|
||||
print(" Exiting.\n")
|
||||
exit()
|
||||
|
||||
|
||||
######################################################################
|
||||
# MAIN LOOP #
|
||||
######################################################################
|
||||
|
||||
while True:
|
||||
try:
|
||||
system("clear")
|
||||
print_menu()
|
||||
selection = int(raw_input("1..8: "))
|
||||
|
||||
if selection == 1:
|
||||
raspbian_txm()
|
||||
|
||||
if selection == 2:
|
||||
ubuntu_txm()
|
||||
|
||||
if selection == 3:
|
||||
raspbian_hwrng()
|
||||
|
||||
if selection == 4:
|
||||
raspbian_rxm()
|
||||
|
||||
if selection == 5:
|
||||
ubuntu_rxm()
|
||||
|
||||
if selection == 6:
|
||||
ubuntu_nh()
|
||||
|
||||
if selection == 7:
|
||||
tails_nh()
|
||||
|
||||
if selection == 8:
|
||||
local_testing()
|
||||
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nExiting.\n")
|
||||
exit()
|
|
@ -0,0 +1,211 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# TFC-NaCl || test_nh.py
|
||||
|
||||
"""
|
||||
GPL License
|
||||
|
||||
This software is part of the TFC application, which 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. For
|
||||
a copy of the GNU General Public License, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import NH
|
||||
from NH import *
|
||||
from binascii import hexlify
|
||||
from hashlib import sha256
|
||||
|
||||
|
||||
###############################################################################
|
||||
# UNITTEST HELPERS #
|
||||
###############################################################################
|
||||
|
||||
def ut_sha2_256(message):
|
||||
h_function = sha256()
|
||||
h_function.update(message)
|
||||
return hexlify(h_function.digest())
|
||||
|
||||
|
||||
###############################################################################
|
||||
# HELPERS #
|
||||
###############################################################################
|
||||
|
||||
NH.unittesting = True
|
||||
|
||||
|
||||
class TestPhase(unittest.TestCase):
|
||||
|
||||
def test_1_input_parameters(self):
|
||||
for a in [1, 1.0, True]:
|
||||
for b in ["string", 1.0, True]:
|
||||
with self.assertRaises(SystemExit):
|
||||
phase(a, b)
|
||||
|
||||
def test_2_output_type(self):
|
||||
self.assertIsNone(phase("test", 10))
|
||||
|
||||
|
||||
class TestSHA256(unittest.TestCase):
|
||||
|
||||
def test_1_input_parameter(self):
|
||||
for a in [1, 1.0, True]:
|
||||
with self.assertRaises(SystemExit):
|
||||
sha2_256(a)
|
||||
|
||||
def test_2_SHA256_vector(self):
|
||||
"""
|
||||
Test SHA256 with official test vector:
|
||||
http://csrc.nist.gov/groups/ST/toolkit/
|
||||
documents/Examples/SHA_All.pdf // page 14
|
||||
"""
|
||||
|
||||
self.assertEqual(sha2_256("abc"), "ba7816bf8f01cfea414140de5dae2223"
|
||||
"b00361a396177a9cb410ff61f20015ad")
|
||||
|
||||
|
||||
class TestVerifyChecksum(unittest.TestCase):
|
||||
|
||||
def test_1_input_parameter(self):
|
||||
for a in [1, 1.0, True]:
|
||||
with self.assertRaises(SystemExit):
|
||||
verify_checksum(a)
|
||||
|
||||
def test_2_function(self):
|
||||
pt = "test_packet"
|
||||
tv = ut_sha2_256(pt)
|
||||
self.assertTrue(verify_checksum("%s|%s" % (pt, tv[:NH.checksum_len])))
|
||||
|
||||
|
||||
class TestCleanExit(unittest.TestCase):
|
||||
|
||||
def test_1_input_parameter(self):
|
||||
for a in [1, 1.0, True]:
|
||||
with self.assertRaises(SystemExit):
|
||||
clean_exit(a)
|
||||
|
||||
def test_2_exit_no_msg(self):
|
||||
with self.assertRaises(SystemExit):
|
||||
clean_exit()
|
||||
|
||||
def test_3_exit_with_msg(self):
|
||||
with self.assertRaises(SystemExit):
|
||||
clean_exit("test message")
|
||||
|
||||
|
||||
class TestGracefulExit(unittest.TestCase):
|
||||
|
||||
def test_1_function(self):
|
||||
with self.assertRaises(SystemExit):
|
||||
graceful_exit()
|
||||
|
||||
|
||||
class TestGetTTyWH(unittest.TestCase):
|
||||
|
||||
def test_output_types(self):
|
||||
w, h = get_tty_wh()
|
||||
|
||||
self.assertTrue(isinstance(w, int))
|
||||
self.assertTrue(isinstance(h, int))
|
||||
|
||||
|
||||
class TestPrintBanner(unittest.TestCase):
|
||||
|
||||
def test_output_type(self):
|
||||
self.assertIsNone(print_banner())
|
||||
|
||||
|
||||
class TestGetSerialInterfaces(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
|
||||
###############################################################################
|
||||
# RECEIVER #
|
||||
###############################################################################
|
||||
|
||||
class TestDBusReceiver(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
|
||||
class TestPidginToRxMQueue(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
|
||||
class TestPidginReceiverProcess(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
|
||||
###############################################################################
|
||||
# OTHER #
|
||||
###############################################################################
|
||||
|
||||
class TestHeaderPrinterProcess(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
|
||||
class TestNHSideCommandProcess(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
|
||||
class TestQueueToPidginProcess(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
|
||||
class NHToRxMSenderProcess(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
|
||||
###############################################################################
|
||||
# PACKETS FROM TxM #
|
||||
###############################################################################
|
||||
|
||||
class TestChooseTxMPacketQueues(unittest.TestCase):
|
||||
|
||||
def test_1_input_parameter(self):
|
||||
for a in [1, 1.0, True]:
|
||||
with self.assertRaises(SystemExit):
|
||||
choose_txm_packet_queues(a)
|
||||
|
||||
|
||||
class TestTxMPacketLoadProcess(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
|
||||
class TestRxMPortListener(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
|
||||
class TestTxMPortListener(unittest.TestCase):
|
||||
"""
|
||||
This function doesn't have any tests yet.
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(exit=False)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,412 @@
|
|||
TFC NaCl 0.16.01 | Update log
|
||||
|
||||
Local key transmission: Bootstrapping security
|
||||
----------------------------------------------
|
||||
In the initial bootstrap, Tx.py generates 256-bit local key and 256-bit key
|
||||
encryption keys with separate processes, where 32-byte /dev/urandom string is
|
||||
hashed with SHA3-256 (simplesha3 library), that is then passed through 25k
|
||||
iteration PBKDF2-HMAC-SHA256.
|
||||
|
||||
If Tx.py is run on a Raspbian (Raspberry Pi), user can mix entropy from free
|
||||
hardware design HWRNG, connected to GPIO 4 of the SoC board into the PBKDF2
|
||||
function. Entropy is sampled with 10Hz frequency and von Neumann whitened
|
||||
during sampling. Once the vN whitened samples have been collected, the
|
||||
256-bit entropy is compressed using SHA3-256 hash function.
|
||||
|
||||
If Tx.py is run on a computer that has direct ethernet connection to Raspberry
|
||||
Pi with GPIO HWRNG, Tx.py can SSH into that computer using paramiko library,
|
||||
and query entropy with program hwrng.py.
|
||||
|
||||
The local key is padded to 254 bytes and encrypted with the key encryption key
|
||||
using PyCrypto library's XSalsa20 stream cipher (192 bit nonce), and 128-bit
|
||||
Poly1305 MAC. The signed ciphertext is transmitted to RxM through data diode.
|
||||
User can choose whether to leave data diodes connected to NH and let signed
|
||||
ciphertext pass through, or whether they want to transmit keys directly from
|
||||
TxM to RxM by connecting serial interfaces that way. Such configuration
|
||||
prevents adversary from obtaining the signed ciphertext from NH in case it has
|
||||
been compromised at this point.
|
||||
|
||||
RxM will then ask user to manually type the 64 hex char key decryption key
|
||||
displayed by TxM. The key decryption key is concatenated with 8 hex char
|
||||
checksum, that is the truncated SHA3-256 hash of the key decryption key.
|
||||
|
||||
Once the 72 char hex key has been typed to Rx.py, the program decrypts the
|
||||
local key, adds it to database, and shows a two char hex code that lets TxM
|
||||
know local key delivery succeeded. This is a pre-requisite for the next step,
|
||||
where account data/keys are transmitted to RxM, encrypted with the local key.
|
||||
|
||||
If RxM does not receive the encrypted local key, user can send the packet again
|
||||
by typing "replay" instead of the device code. The local key transmission
|
||||
can later be redone with command '/localkey'.
|
||||
|
||||
First contact
|
||||
-------------
|
||||
|
||||
User is guided when adding first contact. Tx.py first prompts for account, e.g.
|
||||
alice@jabber.org and a nick. Empty nick uses nick 'Alice' (automatically parsed
|
||||
from account). Tx.py asks user to confirm account and nick (choosing no jumps
|
||||
to beginning of account creation). After this Tx.py prompts user to choose key
|
||||
exchange method:
|
||||
|
||||
Use PSK instead of ECDHE (y/n)?
|
||||
|
||||
PSK is explained later so here we choose 'n'/'no' to start ECDHE key exchange.
|
||||
|
||||
|
||||
ECDHE Private key generation
|
||||
----------------------------
|
||||
Tx.py generates a curve 25519 ECDHE private key by with PyNaCl's
|
||||
PrivateKey.generate(), that has been modified to XOR in either bitstring of 256
|
||||
zeroes, or if provided, 256 bits string of external entropy. Tx.py always
|
||||
provides the external entropy:
|
||||
|
||||
ext_ent = PBKDF2-HMAC-SHA256(SHA3-256(32-bytes from /dev/urandom, salt)).
|
||||
|
||||
If Raspbian with HWRNG is available, Tx.py will add the salt to PBKDF2-mix:
|
||||
|
||||
salt = SHA3-256( Von Neumann whitening (HWRNG samples))
|
||||
|
||||
The number of samples is dynamic and depends on results. This ensures SHA3
|
||||
always takes in 256 Von Neumann whitened samples. If HWRNG produces bad entropy
|
||||
i.e. chunk of just zeroes / ones, they are not added as samples.
|
||||
|
||||
PyNaCl's PrivateKey.generate()'s own entropy is loaded after external entropy
|
||||
is provided, thus even if external entropy adds zero entropy, it does not
|
||||
weaken the overall security, as in such case it would be the equal of XORing
|
||||
PyNaCl's entropy with bitstring of zeroes.
|
||||
|
||||
Once the final entropy has been generated, PyNaCl will use it to create a
|
||||
private key object.
|
||||
|
||||
|
||||
ECDHE Public key delivery and verification of key authenticity
|
||||
--------------------------------------------------------------
|
||||
Tx.py generates the ECDHE public key from the private key object, and transmits
|
||||
it to the recipient's RxM. Once the 64 hex char public key of contact has been
|
||||
received by Rx.py the program shows it concatenated with an eight char hex
|
||||
checksum like the one in local key. User must manually type the 72 hex char
|
||||
string to TxM. Manual process ensures no automated data channel from network
|
||||
can infect TxM after setup.
|
||||
|
||||
Once key has been successfully entered, Tx.py will ask user to use Signal by
|
||||
Open Whisper Systems to verify the authenticity of contact's public key.
|
||||
|
||||
MITM against OTR session is possible if the adversary has has exfiltrated
|
||||
respective keys from end points of users. If users find out during Signal call
|
||||
that the public keys have been generated by a man in the middle, they do not
|
||||
have to throw in the towel. Instead, users can read the public keys to each
|
||||
other over Signal, manually type in new public key and effectively bypass the
|
||||
MITM in network. Once the public key has been successfully typed, Tx.py will
|
||||
log the public keys for later, higher assurance verification done face to face.
|
||||
This also enables retrospective detection of MITM, if for some reason users are
|
||||
unable to verify authenticity of public keys during key exchange.
|
||||
|
||||
|
||||
Shared secret derivation
|
||||
------------------------
|
||||
TxM is the only device that receives no automatically input data after setup,
|
||||
so it is the only device trusted to generate secret keys. Tx.py will generate
|
||||
two symmetric keys (one for encryption, one for decryption) by concatenating
|
||||
ECDHE shared secret with one of the public keys, and passing them through
|
||||
PBKDF2-HMAC-SHA256 (25 000 iterations). This is mandatory for the forward
|
||||
secrecy to work. Forward secrecy is obtained by passing the encryption keys
|
||||
through PBKDF2-HMAC-SHA256 (1 000 iterations) between every message. Were the
|
||||
ECDHE shared secret used as symmetric key for communication in both directions,
|
||||
offset in key derivation could lead to decryption of collected ciphertexts by
|
||||
iterating physically compromised key, that lacks behind in PBKDF2 iterations.
|
||||
|
||||
Once the symmetric keys are generated, the account details are encrypted and
|
||||
signed with XSalsa20-Poly1305 using the local key, and transmitted to RxM,
|
||||
where the contact is automatically added. While this packet doesn't differ in
|
||||
any way from encrypted command, user can still choose to prevent NH from
|
||||
receiving the ciphertext by plugging the TxM's data diode directly to RxM once
|
||||
the public key of contact has been received to RxM. During this period any
|
||||
message sent by contacts is not received. Dropped packets do not cause
|
||||
de-synchronisation of keys, as each packet contains the number of times Tx.py
|
||||
has iterated the initial symmetric encryption key with PBKDF2. This means both
|
||||
receiving devices can catch up with the Tx.py's key. If Rx.py detects dropped
|
||||
packets, it will display the catch up progress. KeyID introduces a DoS attack
|
||||
vector where packets with great keyID blocks Rx.py from operating. This would
|
||||
however require a MITM attack against the OTR, or messing with the NH; Such DoS
|
||||
would blow the cover of the high-strength attacker. A more covert DoS can be
|
||||
mitigated from within the network or IM server, so this attack is an unlikely
|
||||
problem. Were this evaluation incorrect, an efficient fix would be to use a
|
||||
separate static MAC key that signs the keyID. For now, it is not implemented.
|
||||
It should be mentioned that this DoS attack can also be mitigated by frenemies,
|
||||
but they are easy to block from the IM client.
|
||||
|
||||
Additional accounts can be added with commands
|
||||
/dh <account>( <nick>)
|
||||
and
|
||||
/add <account>( <nick>)
|
||||
|
||||
|
||||
PSK keys
|
||||
--------
|
||||
While TFC-NaCl is all about convenient public key crypto with exfiltration
|
||||
secure private keys, it's security will not hold in the long run. Quantum
|
||||
computers are making their way albeit slowly, and in the future any symmetric
|
||||
key agreed using Curve25519 ECDHE will be broken. The only public key algorithm
|
||||
secure against Shor's quantum algorithm is McEliece with Goppa Codes. Long term
|
||||
security would require users to type in 1 000 000 bit public keys, which is
|
||||
highly inconvenient regardless of encoding used. As an initial answer to
|
||||
quantum computing, TFC-NaCl retains the possibility to create pre-shared
|
||||
symmetric keys for quantum-secure 256-bit XSalsa20-Poly1305:
|
||||
|
||||
Answering yes to PSK question in the beginning will create symmetric keys with
|
||||
PBKDF-HMAC-SHA256 (25 000 iterations), using SHA3-256(urandom(32)) as password,
|
||||
and if HWRNG connected to Raspbian (SSH or not) is available, by using
|
||||
SHA3-512(VN(256-bit HWRNG entropy)) as salt.
|
||||
|
||||
The contact's account, nick and the symmetric key for local decryption is sent
|
||||
to RxM, encrypted with local key (again, NH doesn't have to be in between if
|
||||
user so prefers). Tx.py then prompts user for his or her account name.
|
||||
|
||||
If Alice is adding bob@jabber.org as contact, a copy of her symmetric key is
|
||||
generated for Bob, and conveniently placed inside folder "PSKs", under the name
|
||||
|
||||
rx.alice@jabber.org.e - Give this file to bob@jabber.org
|
||||
|
||||
Alice must then take a never before used thumb drive, copy the PSK to that and
|
||||
give it to Bob in a face-to-face meeting. Bob must also have generated a PSK
|
||||
for Alice in advance. Once Alice has received the thumb drive from Bob, she
|
||||
plugs it into her RxM (NOT TxM), and copies the
|
||||
|
||||
rx.bob@jabber.org.e - Give this file to alice@jabber.org
|
||||
|
||||
keyfile to folder "keys". She must then restart Rx.py. Rx.py automatically
|
||||
strips the trailing instruction from the keyfile and adds Bob as contact. Since
|
||||
keys for encrypting messages to Bob are already installed, once Bob has copied
|
||||
keyfile generated by Alice to his RxM, they are ready to communicate.
|
||||
|
||||
To ensure forward secrecy and privacy of messages, both parties MUST ensure the
|
||||
thumb drive given by their contact is physically destroyed immediately after
|
||||
keys have been added.
|
||||
|
||||
If contacts have already been generated, new PSKs can be generated with command
|
||||
|
||||
/psk <account>( <nick>)
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Other updates over TFC 0.5.5:
|
||||
|
||||
-Change of version style to 0.16.01 (0.YY.MM).
|
||||
-Fully PEP8 compliant coding style/variable naming.
|
||||
|
||||
|
||||
New local testing IPC
|
||||
---------------------
|
||||
Local testing of TFC with single computer now uses multiprocessing sockets
|
||||
instead of files. This reduces IO errors also means less files in TFC root
|
||||
directory.
|
||||
|
||||
|
||||
Data diode simulators
|
||||
---------------------
|
||||
Added dd.py program that emulates data diodes on screen. User
|
||||
needs to enable dd_sockets boolean in Tx.py and NH.py with argument -d
|
||||
before this can take place. Each dd.py program is launched with a set of
|
||||
arguments so that they know which sockets to use and to what direction data
|
||||
visually flows between terminals. An example set of commands that launches TFC
|
||||
on *buntu is
|
||||
|
||||
gnome-terminal --title='TxM' --geometry=100x35+0+630 -x sh -c "python /dir/Tx.py -d"
|
||||
gnome-terminal --title='NH' --geometry=71x41+920+150 -x sh -c "python /dir/NH.py -d"
|
||||
gnome-terminal --title='RxM' --geometry=100x20+0+0 -x sh -c "python /dir/Rx.py"
|
||||
gnome-terminal --title='dd' --geometry=25x12+740+630 -x sh -c "python /dir/dd.py txnhlr"
|
||||
gnome-terminal --title='dd' --geometry=25x12+740+425 -x sh -c "python /dir/dd.py nhrxlr"
|
||||
|
||||
Setting keyboard shortcut or alias for each of these makes local test startup
|
||||
fast.
|
||||
|
||||
|
||||
Serial interface auto-config
|
||||
----------------------------
|
||||
User's device configuration is asked during installation. This will configure
|
||||
Tx.py, NH.py and Rx.py to look for either integrated ttyS# (or ttyAMA0 in the
|
||||
case of Raspbian) serial interface, or for a USB interface (ttyUSB#) if user
|
||||
decides to use those. The serial interface numbering is finally automatically
|
||||
detected, so if user accidentally pulls out serial interface, user doesn't have
|
||||
to manually edit the interface if OS remaps /dev/ttyUSB0 to /dev/ttyUSB1.
|
||||
|
||||
|
||||
NH interface auto-flip
|
||||
----------------------
|
||||
Removed the need to run NH.py with -f flag to flip interfaces:
|
||||
Tx.py will send a serial interface configuration packet to NH.py during start.
|
||||
In the event NH.py has mapped TxM and RxM in opposite interfaces, initial
|
||||
listener processes detect Tx.py's configuration packet (or any packet for that
|
||||
matter), and map correct interfaces to TxM and RxM.
|
||||
|
||||
|
||||
Organized files to folders
|
||||
--------------------------
|
||||
Moved location of group files, keyfiles, received files and logfiles to
|
||||
respective folders. Renamed txc.tfc and rxc.tfc as dotfiles .tx_contacts and
|
||||
.rx_contacts. The only TFC files visible in software root directory are .py
|
||||
files (and syslog.tfc once it's first generated).
|
||||
|
||||
|
||||
Added some metadata about TFC to packet headers
|
||||
-----------------------------------------------
|
||||
In future this prevents interoperation with older versions of TFC. It allows
|
||||
fingerprinting of users (ONLY if NH is remotely exploited or OTR is being MITM
|
||||
attacked), but forces users to keep up with security updates. New protocol uses
|
||||
more simple and mature separation of payload components.
|
||||
|
||||
|
||||
Re-designed trickle connection
|
||||
------------------------------
|
||||
input_process() now prepares all data to packets with length in range [0, 253]
|
||||
and puts the data into message or file queue. The queue is periodically read by
|
||||
sender_process() that opens a thread that padds, encrypts, signs, adds headers
|
||||
and outputs messages. XSalsa20-Poly1305 algorithms are designed to run constant
|
||||
time. The system time in ms is recorded immediately before function starts;
|
||||
Once the function finishes, the timestamp is passed to function trickle_delay()
|
||||
for evaluation. This function will then sleep the difference between Thread
|
||||
runtime and value trickle_c_delay to hide platform speed. Platforms should not
|
||||
introduce problems, as older Raspberry Pi (generation 2), only takes about
|
||||
400ms to encrypt a message, leaving 1600ms headroom for even slower SoC boards.
|
||||
|
||||
Changed random sleep to use /dev/urandom instead of Python math.random. The
|
||||
CSPRNG prevents statistical attacks that might be able to calculate internal
|
||||
state of math.random() to detect whether communication is taking place. When
|
||||
print_ct_stats boolean is enabled, Tx.py shows the statistics about how long
|
||||
the message/command output thread ran, how long constant time sleep was added,
|
||||
and how well the constant time matched trickle_c_delay: Average error in
|
||||
constant time delay was 2ms so the CSPRNG based random sleep will hide the
|
||||
slight differences effectively. Tx.py will gracefully exit if thread runtime
|
||||
exceeds trickle_c_delay.
|
||||
|
||||
The packet_delay used to prevent congestion / IM server spam with long messages
|
||||
is now also constant time, to prevent slower data transmission with slower
|
||||
devices. Default value is 500ms which should be enough to hide metadata about
|
||||
TxM device performance and make hardware fingerprinting harder. This also makes
|
||||
prediction of file transfer duration easier.
|
||||
|
||||
|
||||
Improved logging
|
||||
----------------
|
||||
Logging can now be enabled for individual contacts with command
|
||||
/logging {on,off} <account>. When logging is enabled, in addition to automatic
|
||||
logging of public keys, Tx.py will also log messages sent to contact. This
|
||||
allows the two users to manually audit whether malware has at some point
|
||||
infected RxM device(s) and substituted words in logfiles, by cross-comparing
|
||||
the clean TxM logfile of Alice and purported RxM logfile of Bob, and vice
|
||||
versa. Rx.py also stores information about dropped packets. Malware that
|
||||
replaces received messages with notification about dropped packet is harder to
|
||||
detect; offline record keeping might be required.
|
||||
|
||||
|
||||
Tweakable checksums for data diode error detection
|
||||
--------------------------------------------------
|
||||
Changed CRC32 checksums to truncated SHA-256 hashes (8 hex = 32 bits) so
|
||||
data diode error detection accuracy can be tweaked at will by changing
|
||||
variable checksum_len. The checksum doesn't have any effect on security:
|
||||
it only detects transmission errors inside datadiode.
|
||||
|
||||
|
||||
Rewritten NH.py
|
||||
---------------
|
||||
Converted classes to functions to fit with procedural programming style. The
|
||||
program is now much more structured and easier to audit. Added processes that
|
||||
exchange data via queues. DBus / purple should no longer output error messages,
|
||||
nor should they drop packets. NH multiprocessing now cleanly exits.
|
||||
|
||||
|
||||
Re-designed file transmission.
|
||||
------------------------------
|
||||
/file 1.txt still sends the .txt file from TxM directory. Absolute paths can
|
||||
also be defined. Non-TFC filenames in Tx.py directory are now included in
|
||||
tab-complete list. Command "/file" opens a file dialog for easy file selection.
|
||||
Tx.py will first encode file data to base64 and load it to memory. It'll
|
||||
evaluate and display approximate amount of packets and time required for file
|
||||
transfer, based on delay settings.
|
||||
|
||||
File data is prepended with a header that contains the name, extension, file
|
||||
size and estimated number of packets and delivery time for transfer. This
|
||||
information is displayed to recipient after first packet of file has been
|
||||
received. File reception must be enabled enabled.
|
||||
|
||||
User can control global file reception by setting boolean file_saving to True.
|
||||
/store {on,off} enables/disables file reception for all contacts
|
||||
/store on alice@jabber.org enables file reception for just Alice
|
||||
|
||||
By default boolean setting a_close_f_recv is True. This means file reception
|
||||
for Alice is closed automatically after file has been received. When the
|
||||
variable is false, file storage will remain open until user enters
|
||||
/store off alice@jabber.org
|
||||
/store off
|
||||
|
||||
During trickle connection, messages and files can be cancelled with commands
|
||||
'/cm' and '/cf'. Already sent data is out of control of user. Rx.py will
|
||||
discard received data when it receives cancel packet / new message/file, but
|
||||
this is a convenience feature, NOT a security feature. Sender-based control is
|
||||
snake oil: Nothing prevents receiver from modifying their Rx.py to show/log
|
||||
partially transmitted data. The only solution to this would be to offer closed
|
||||
source version of TFC, which would defeat all security free software is
|
||||
designed to offer.
|
||||
|
||||
Since the base64 encoding of transmitted files depends on TxM of contact, a
|
||||
frenemy could still send malware to user's RxM. Thus, the entire manual
|
||||
decoding feature was removed. File is no longer generated with subprocess.
|
||||
This prevents shell injection with custom file names that would have otherwise
|
||||
become possible.
|
||||
|
||||
File data during trickle connection is loaded from separate queue that has
|
||||
lower priority than message queue. This means that users can still exchange
|
||||
messages during file transmission: file data is output to contact when there
|
||||
are no messages to output. File data is indistinguishable from messages to NH,
|
||||
IM client, IM server and any adversary who might observe data from these
|
||||
systems. During trickle connection, it is extremely hard to determine whether
|
||||
user is sending noise data, messages or files. No guarantees can unfortunately
|
||||
be made. Timing attacks are prone to side channel attacks anywhere from
|
||||
NH/smartphone microphone to user moving the mouse on NH. Python is also a
|
||||
high level language, which has it's own problems.
|
||||
|
||||
|
||||
Command line arguments
|
||||
----------------------
|
||||
Added arguments for Tx.py, Rx.py and NH.py to control many settings from
|
||||
command line. View arguments by launching program with -h/--help flag.
|
||||
|
||||
|
||||
Hard coded packet length
|
||||
------------------------
|
||||
Packet length was hard coded to 254 to ensure everyone uses same value,
|
||||
and to minimize chance for errors when sending encrypted commands. The
|
||||
adjustable length was a legacy feature necessary only in TFC-OTP: Key material
|
||||
is not a problem with symmetric crypto, thus adjusting key material expenditure
|
||||
is no longer necessary.
|
||||
|
||||
|
||||
Unittesting
|
||||
-----------
|
||||
Wrote almost 400 unittests (5600 LoC) for Tx.py, Rx.py and NH.py to reduce
|
||||
the amount of bugs. Created custom error classes for some functions for this
|
||||
purpose as well. Unittesting is a work in progress, so while TFC is more stable
|
||||
than ever, no guarantees are made.
|
||||
|
||||
|
||||
New startup banner animation
|
||||
----------------------------
|
||||
I want to thank Tom Wallroth for his awesome work. I had so much fun editing
|
||||
his project to fit print_banner(). I'll leave the details of style as a
|
||||
surprise, check it out.
|
||||
|
||||
|
||||
Made edits to installer
|
||||
-----------------------
|
||||
Installer now uses SHA256 instead of SHA512. As collision resistance against
|
||||
quantum computers is still 128 bits. While weaker, the security is as strong
|
||||
as the 256-bit XSalsa20 used. The reason for this was simplified import of
|
||||
file hashes when making changes.
|
||||
|
||||
Added installation configuration for HWRNG Pi that TxM connects to over SSH.
|
||||
Ensured support for latest *buntu operating systems and fixed issues with
|
||||
Lubuntu/Xubuntu in previous versions. Dropped NH support for Fedora and
|
||||
OpenSUSE. Ensured NH configuration works with latest release of Tails (2.0).
|
||||
|
||||
Changed installer naming style, instead of tfcNaClinstaller.py, it's just
|
||||
setup.py.
|
Loading…
Reference in New Issue