diff --git a/README.md b/README.md index 02fc7ca..2336fa3 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ Note that it is software, currently in alpha stage. # Screenshot -Below a sample screenshot. More screenshots [here](https://github.com/8go/TrezorSymmetricFileEncryption/tree/master/screenshots). +Below a sample screenshot. More screenshots [here](screenshots). -![screenshot](https://github.com/8go/TrezorSymmetricFileEncryption/blob/master/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow2.version03b.png) +![screenshot](screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow6.version04b.png) # Build and runtime requirements @@ -74,7 +74,7 @@ Run: Run-time command line options are ``` -TrezorSymmetricFileEncryption.py [-v] [-h] [-l ] [-t] [-2] [-s] [-w] [-o | -e | -d | -n] [-p ] +TrezorSymmetricFileEncryption.py [-v] [-h] [-l ] [-t] [-2] [-s] [-w] [-o | -e | -d | -m | -n] [-p ] -v, --verion print the version number -h, --help @@ -113,9 +113,11 @@ TrezorSymmetricFileEncryption.py [-v] [-h] [-l ] [-t] [-2] [-s] [-w] [-o only relevant for `-e` and `-o`; ignored in all other cases. Primarily useful for testing. -w, --wipe - shred the plaintext file after encryption; - only relevant for `-e` and `-o`; ignored in all other cases. - Use with caution. May be used together with `-s`. + shred the inputfile after creating the output file + i.e. shred the plaintext file after encryption or + shred the encrypted file after decryption; + only relevant for `-d`, `-e` and `-o`; ignored in all other cases. + Use with extreme caution. May be used together with `-s`. one or multiple files to be encrypted or decrypted @@ -164,6 +166,10 @@ TrezorSymmetricFileEncryption.py [-v] [-h] [-l ] [-t] [-2] [-s] [-w] [-o # encrypt contract and obfuscate output producing e.g. TQFYqK1nha1IfLy_qBxdGwlGRytelGRJ TrezorSymmetricFileEncryption.py -o contract.doc + # encrypt contract and obfuscate output producing e.g. TQFYqK1nha1IfLy_qBxdGwlGRytelGRJ + # performs safety check and then shreds contract.doc + TrezorSymmetricFileEncryption.py -e -o -s -w contract.doc + # decrypt contract producing contract.doc TrezorSymmetricFileEncryption.py contract.doc.tsfe @@ -172,6 +178,18 @@ TrezorSymmetricFileEncryption.py [-v] [-h] [-l ] [-t] [-2] [-s] [-w] [-o # shows plaintext name of encrypted file, e.g. contract.doc TrezorSymmetricFileEncryption.py -n TQFYqK1nha1IfLy_qBxdGwlGRytelGRJ + + Keyboard shortcuts of GUI: + Apply, Save: Control-A, Control-S + Cancel, Quit: Esc, Control-Q + Copy to clipboard: Control-C + Version, About: Control-V + Set encrypt operation: Control-E + Set decrypt operation: Control-D + Set obfuscate option: Control-O + Set twice option: Control-2 + Set safety option: Control-T + Set wipe option: Control-W ``` # Testing diff --git a/TrezorSymmetricFileEncryption.py b/TrezorSymmetricFileEncryption.py index 6227287..7a9eaed 100755 --- a/TrezorSymmetricFileEncryption.py +++ b/TrezorSymmetricFileEncryption.py @@ -147,7 +147,7 @@ def chooseDevice(self, devices): deviceStr = dialog.chosenDeviceStr() return HidTransport([deviceStr, None]) -def showGui(trezor, settings, fileMap, logger, feedback): +def showGui(trezor, settings, fileMap, logger): """ Initialize, ask for encrypt/decrypt options, ask for files to be decrypted/encrypted, @@ -161,27 +161,20 @@ def showGui(trezor, settings, fileMap, logger, feedback): items selected in GUI """ dialog = Dialog(trezor, settings, fileMap, logger) - settings.guiExists = True settings.settings2Gui(dialog, trezor) - if settings.logger.getEffectiveLevel() <= logging.INFO: - dialog.appendDescription("
Trezor label: " + trezor.features.label) - if settings.WArg and settings.logger.getEffectiveLevel() <= logging.WARN: - dialog.appendDescription("
Warning: The option `--wipe` is set. Plaintext files will " - "be shredded after encryption. Abort if you are uncertain or don't understand.") + processing.reportLogging("Trezor label: %s" % trezor.features.label, logging.INFO, + "Trezor IO", settings, logger, dialog) if not dialog.exec_(): - settings.guiExists = False processing.reportLogging("Shutting down due to user request " "(Done/Quit was called).", logging.DEBUG, - "GUI IO", settings, logger) + "GUI IO", settings, logger, None) sys.exit(4) # Esc or exception - settings.guiExists = False settings.gui2Settings(dialog,trezor) # root logging.basicConfig(stream=sys.stderr, level=basics.LOGGINGLEVEL) logger = logging.getLogger('tsfe') -feedback = processing.Feedback() app = QtGui.QApplication(sys.argv) @@ -204,20 +197,27 @@ def showGui(trezor, settings, fileMap, logger, feedback): trezor.clear_session() -if settings.TArg: - logger.info("Trezor label: %s", trezor.features.label) - fileMap = file_map.FileMap(trezor,logger) # if everything is specified in the command line then do not call the GUI if ((settings.PArg is None) or (len(settings.inputFiles) <= 0)) and (not settings.TArg): # something was not specified, so we call the GUI - showGui(trezor, settings, fileMap, logger, feedback) + showGui(trezor, settings, fileMap, logger) else: - logger.info("Everything was specified or --terminal was set, " - "hence the GUI will not be called.") + processing.reportLogging("Trezor label: %s" % trezor.features.label, logging.INFO, + "Trezor IO", settings, logger, None) + processing.reportLogging("Everything was specified or --terminal was set, " + "hence the GUI will not be called.", logging.INFO, + "Trezor IO", settings, logger, None) + if settings.WArg: + processing.reportLogging("The option `--wipe` is set. In case of " + "encryption, the original plaintext files will " + "be shredded after encryption. In case of decryption, " + "the encrypted files will be shredded after decryption. " + "Abort if you are uncertain or don't understand.", logging.WARNING, + "Dangerous arguments", settings, logger, None) if settings.PArg is not None: trezor.prefillPassphrase(settings.PArg) - processing.processAll(trezor, settings, fileMap, logger, feedback) + processing.processAll(trezor, settings, fileMap, logger, dialog=None) sys.exit(0) diff --git a/basics.py b/basics.py index 891ce33..4d7e30f 100644 --- a/basics.py +++ b/basics.py @@ -7,7 +7,10 @@ TSFEFILEFORMATVERSION = 1 # Name of software version, must be less than 16 long -TSFEVERSION = "v0.3.2" +TSFEVERSION = "v0.4" + +# Date of software version, only used in GUI +TSFEVERSIONTEXT = "May 2017" # default log level LOGGINGLEVEL = logging.INFO # CRITICAL, ERROR, WARNING, INFO, DEBUG diff --git a/dialog.ui b/dialog.ui index 8b3cdb3..390f57a 100644 --- a/dialog.ui +++ b/dialog.ui @@ -6,8 +6,8 @@ 0 0 - 600 - 770 + 840 + 820 @@ -18,8 +18,8 @@ - 400 - 600 + 800 + 700 @@ -67,49 +67,459 @@ - Choose one option (if empty it will be chosen automatically) + Choose one operation and multiple options (if empty all will be chosen automatically) - - - Encrypt file once and leave filename in plaintext - - - - - - - Encrypt file once and obfuscate filename + + + Qt::Horizontal - - - Encrypt file twice and leave filename in plaintext + + + QLayout::SetDefaultConstraint - + + + + 6 + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 75 + true + + + + Chose Encrypt Operation + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 25 + + + + Encrypt file + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 25 + + + + Show only obfuscated filename (without encrypting file) + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 65 + + + + + + + + + + 6 + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 75 + true + + + + Chose Encrypt Options + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 25 + + + + Obfuscate filename + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 25 + + + + Encrypt twice (very slow on large files) + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 25 + + + + Perform safety check on encrypted file + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 25 + + + + Shred plaintext file after encryption + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 0 + + + + + + + - - - Encrypt file twice and obfuscate filename + + + Qt::Horizontal - - - Decrypt file + + + QLayout::SetMinimumSize - + + + + 6 + + + QLayout::SetFixedSize + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 75 + true + + + + Chose Decrypt Operation + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Decrypt file + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Decrypt only obfuscated filename (but not the file) + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 0 + + + + + + + + + + 6 + + + QLayout::SetFixedSize + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 75 + true + + + + Chose Decrypt Options + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + Shred encrypted file after decryption + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 33 + + + + + + + - - - Decrypt only obfuscated filename (but not the file) + + + Qt::Horizontal diff --git a/dialogs.py b/dialogs.py index f6b7064..f5e5510 100644 --- a/dialogs.py +++ b/dialogs.py @@ -5,6 +5,7 @@ import logging from PyQt4 import QtGui, QtCore +from PyQt4.QtGui import QPixmap from ui_trezor_passphrase_dialog import Ui_TrezorPassphraseDialog from ui_dialog import Ui_Dialog @@ -14,6 +15,8 @@ from encoding import q2s, s2q from processing import processAllFromApply, reportLogging +import basics + class TrezorPassphraseDialog(QtGui.QDialog, Ui_TrezorPassphraseDialog): def __init__(self): @@ -34,12 +37,22 @@ def __init__(self, trezor, settings, fileMap, logger): self.fileMap = fileMap self.logger = logger - self.radioButton1.toggled.connect(self.validate) - self.radioButton2.toggled.connect(self.validate) - self.radioButton3.toggled.connect(self.validate) - self.radioButton4.toggled.connect(self.validate) - self.radioButton5.toggled.connect(self.validate) - self.radioButton6.toggled.connect(self.validate) + self.radioButtonEncFile.toggled.connect(self.validateEncFile) + self.radioButtonEncFilename.toggled.connect(self.validateEncFilename) + self.radioButtonDecFile.toggled.connect(self.validateDecFile) + self.radioButtonDecFilename.toggled.connect(self.validateDecFilename) + self.checkBoxEncO.toggled.connect(self.validateEncO) + self.checkBoxEnc2.toggled.connect(self.validateEnc2) + self.checkBoxEncS.toggled.connect(self.validateEncS) + self.checkBoxEncW.toggled.connect(self.validateEncW) + self.checkBoxDecW.toggled.connect(self.validateDecW) + + self.checkBoxEncO.setEnabled(False) + self.checkBoxEnc2.setEnabled(False) + self.checkBoxEncS.setEnabled(False) + self.checkBoxEncW.setEnabled(False) + self.checkBoxDecW.setEnabled(False) + self.masterEdit1.textChanged.connect(self.validate) self.masterEdit2.textChanged.connect(self.validate) self.selectedFileEdit.textChanged.connect(self.validate) @@ -71,24 +84,151 @@ def __init__(self, trezor, settings, fileMap, logger): QtGui.QShortcut(QtGui.QKeySequence("Ctrl+S"), self, self.accept) # Save QtGui.QShortcut(QtGui.QKeySequence("Ctrl+A"), self, self.accept) # Apply QtGui.QShortcut(QtGui.QKeySequence("Ctrl+C"), self, self.copy2Clipboard) + QtGui.QShortcut(QtGui.QKeySequence("Ctrl+V"), self, self.printAbout) # Version/About + QtGui.QShortcut(QtGui.QKeySequence("Ctrl+E"), self, self.setEnc) # Enc + QtGui.QShortcut(QtGui.QKeySequence("Ctrl+D"), self, self.setDec) # + QtGui.QShortcut(QtGui.QKeySequence("Ctrl+O"), self, self.setEncObf) # + QtGui.QShortcut(QtGui.QKeySequence("Ctrl+2"), self, self.setEncTwice) # + QtGui.QShortcut(QtGui.QKeySequence("Ctrl+T"), self, self.setEncSafe) # + QtGui.QShortcut(QtGui.QKeySequence("Ctrl+W"), self, self.setEncDecWipe) # + QtGui.QShortcut(QtGui.QKeySequence("Ctrl+M"), self, self.setEncFn) # + QtGui.QShortcut(QtGui.QKeySequence("Ctrl+N"), self, self.setDecFn) # self.clipboard = QtGui.QApplication.clipboard() self.textBrowser.selectionChanged.connect(self.selectionChanged) + + def printAbout(self): + """ + Show window with about and version information. + """ + msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Information, "About", + "About TrezorSymmetricFileEncryption:

TrezorSymmetricFileEncryption " + + "is a file encryption and decryption tool using a Trezor hardware " + "device for safety and security. Symmetric AES cryptography is used " + "at its core.

" + + "Version: " + basics.TSFEVERSION + + " from " + basics.TSFEVERSIONTEXT) + msgBox.setIconPixmap(QPixmap("icons/TrezorSymmetricFileEncryption.216x100.svg")) + msgBox.exec_() + + def validateDecFile(self): + if self.checkBoxEncO.isChecked(): + self.checkBoxEncO.setChecked(False) + if self.checkBoxEnc2.isChecked(): + self.checkBoxEnc2.setChecked(False) + if self.checkBoxEncS.isChecked(): + self.checkBoxEncS.setChecked(False) + if self.checkBoxEncW.isChecked(): + self.checkBoxEncW.setChecked(False) + self.checkBoxEncO.setEnabled(False) + self.checkBoxEnc2.setEnabled(False) + self.checkBoxEncS.setEnabled(False) + self.checkBoxEncW.setEnabled(False) + self.checkBoxDecW.setEnabled(True) + self.validate() + + def validateDecFilename(self): + if self.checkBoxEncO.isChecked(): + self.checkBoxEncO.setChecked(False) + if self.checkBoxEnc2.isChecked(): + self.checkBoxEnc2.setChecked(False) + if self.checkBoxEncS.isChecked(): + self.checkBoxEncS.setChecked(False) + if self.checkBoxEncW.isChecked(): + self.checkBoxEncW.setChecked(False) + if self.checkBoxDecW.isChecked(): + self.checkBoxDecW.setChecked(False) + self.checkBoxEncO.setEnabled(False) + self.checkBoxEnc2.setEnabled(False) + self.checkBoxEncS.setEnabled(False) + self.checkBoxEncW.setEnabled(False) + self.checkBoxDecW.setEnabled(False) + self.validate() + + def validateEncFile(self): + if self.checkBoxDecW.isChecked(): + self.checkBoxDecW.setChecked(False) + self.checkBoxEncO.setEnabled(True) + self.checkBoxEnc2.setEnabled(True) + self.checkBoxEncS.setEnabled(True) + self.checkBoxEncW.setEnabled(True) + self.checkBoxDecW.setEnabled(False) + self.validate() + + def validateEncFilename(self): + if self.checkBoxEncO.isChecked(): + self.checkBoxEncO.setChecked(False) + if self.checkBoxEnc2.isChecked(): + self.checkBoxEnc2.setChecked(False) + if self.checkBoxEncS.isChecked(): + self.checkBoxEncS.setChecked(False) + if self.checkBoxEncW.isChecked(): + self.checkBoxEncW.setChecked(False) + if self.checkBoxDecW.isChecked(): + self.checkBoxDecW.setChecked(False) + self.checkBoxEncO.setEnabled(False) + self.checkBoxEnc2.setEnabled(False) + self.checkBoxEncS.setEnabled(False) + self.checkBoxEncW.setEnabled(False) + self.checkBoxDecW.setEnabled(False) + self.validate() + + def validateEncO(self): + if self.checkBoxEncO.isChecked(): + reportLogging("You have selected the option `--obfuscate`. " + "After encrypting the file(s) the encrypted file(s) will be " + "renamed to encrypted, strange looking names. This hides the meta-data," + "i.e. the filename.", logging.INFO, + "Arguments", self.settings, self.logger, self) + + def validateEnc2(self): + if self.checkBoxEnc2.isChecked(): + reportLogging("You have selected the option `--twice`. " + "Files will be encrypted not on your computer but on the Trezor " + "device itself. This is slow. It takes 75 seconds for 1M. " + "In other words, 0.8M/min. " + "Remove this option if the file is too big or " + "you do not want to wait.", logging.INFO, + "Dangerous arguments", self.settings, self.logger, self) + + def validateEncS(self): + if self.checkBoxEncS.isChecked(): + reportLogging("You have selected the option `--safety`. " + "After encrypting the file(s) the file(s) will immediately " + "be decrypted and the output compared to the original file(s). " + "This safety check definitely guarantees that all is well.", logging.INFO, + "Arguments", self.settings, self.logger, self) + + def validateEncW(self): + if self.checkBoxEncW.isChecked(): + reportLogging("You have selected the option `--wipe`. " + "The original plaintext files will " + "be shredded and permanently deleted after encryption. " + "Remove this option if you are uncertain or don't understand.", logging.WARN, + "Dangerous arguments", self.settings, self.logger, self) + + def validateDecW(self): + if self.checkBoxDecW.isChecked(): + reportLogging("You have selected the option `--wipe`. " + "The encrypted files will be shredded and permanently deleted after decryption. " + "Remove this option if you are uncertain or don't understand.", logging.WARN, + "Dangerous arguments", self.settings, self.logger, self) + def selectionChanged(self): """ called whenever selected text in textarea is changed """ # self.textBrowser.copy() # copy selected to clipboard # reportLogging("Copied text to clipboard: %s" % self.clipboard.text(), - # logging.DEBUG, "Clipboard", self.settings, self.logger) + # logging.DEBUG, "Clipboard", self.settings, self.logger, self) """ empty """ def copy2Clipboard(self): self.textBrowser.copy() # copy selected to clipboard # This is content from the Status textarea, so no secrets here, we can log it reportLogging("Copied text to clipboard: %s" % self.clipboard.text(), logging.DEBUG, - "Clipboard", self.settings, self.logger) + "Clipboard", self.settings, self.logger, self) def setVersion(self, version): self.version = version @@ -111,56 +251,84 @@ def appendDescription(self, extradescription): self.description2 += extradescription self.textBrowser.setHtml(s2q(self.description1 + self.description2 + self.description3)) - def enc(self): + def encObf(self): """ - Returns True if radio button for option "encrypt with plaintext filename" + Returns True if radio button for option "encrypt and obfuscate filename" is selected """ - return self.radioButton1.isChecked() + return self.checkBoxEncO.isChecked() - def setEnc(self, arg): - self.radioButton1.setChecked(arg) + def setEncObf(self, arg=True): + self.checkBoxEncO.setChecked(arg) - def encObf(self): + def encTwice(self): + return self.checkBoxEnc2.isChecked() + + def setEncTwice(self, arg=True): + self.checkBoxEnc2.setChecked(arg) + + def encSafe(self): + return self.checkBoxEncS.isChecked() + + def setEncSafe(self, arg=True): + self.checkBoxEncS.setChecked(arg) + + def encWipe(self): + return self.checkBoxEncW.isChecked() + + def setEncWipe(self, arg=True): + self.checkBoxEncW.setChecked(arg) + + def decWipe(self): + return self.checkBoxDecW.isChecked() + + def setDecWipe(self, arg=True): + self.checkBoxDecW.setChecked(arg) + + def setEncDecWipe(self, arg=True): + if self.enc(): + self.setEncWipe(arg) + if self.dec(): + self.setDecWipe(arg) + + def enc(self): """ - Returns True if radio button for option "encrypt and obfuscate filename" + Returns True if radio button for option "encrypt with plaintext filename" is selected """ - return self.radioButton2.isChecked() - - def setEncObf(self, arg): - self.radioButton2.setChecked(arg) + return self.radioButtonEncFile.isChecked() - def setEncTwice(self, arg): - self.radioButton5.setChecked(arg) + def setEnc(self, arg=True): + self.radioButtonEncFile.setChecked(arg) - def setEncTwiceObf(self, arg): - self.radioButton6.setChecked(arg) - - def encTwice(self): - return self.radioButton5.isChecked() + def encFn(self): + """ + Returns True if radio button for option "encrypt with plaintext filename" + is selected + """ + return self.radioButtonEncFilename.isChecked() - def encTwiceObf(self): - return self.radioButton6.isChecked() + def setEncFn(self, arg=True): + self.radioButtonEncFilename.setChecked(arg) def dec(self): """ Returns True if radio button for option "decrypt" is selected """ - return self.radioButton3.isChecked() + return self.radioButtonDecFile.isChecked() - def setDec(self, arg): - self.radioButton3.setChecked(arg) + def setDec(self, arg=True): + self.radioButtonDecFile.setChecked(arg) def decFn(self): """ Returns True if radio button for option "decrypt only filename" is selected """ - return self.radioButton4.isChecked() + return self.radioButtonDecFilename.isChecked() - def setDecFn(self, arg): - self.radioButton4.setChecked(arg) + def setDecFn(self, arg=True): + self.radioButtonDecFilename.setChecked(arg) def pw1(self): return self.masterEdit1.text() @@ -224,19 +392,16 @@ def validate(self): # QtGui.QDialogButtonBox.Ok button = self.buttonBox.button(QtGui.QDialogButtonBox.Apply) - # exactly one is set - # and (self.enc() ^ self.encObf() ^ self.dec()) and not (self.enc() and self.encObf() and self.dec()) button.setEnabled(fileSelected and (same or self.dec() or self.decFn())) return fileSelected and (same or self.dec() or self.decFn()) # def handleButtonClick(self, button): # sb = self.buttonBox.standardButton(button) # if sb == QtGui.QDialogButtonBox.Apply: -# feedback = processAllFromApply(self, self.trezor, self.settings, self.fileMap, self.logger) # -# self.appendDescription(feedback) +# processAllFromApply(self, self.trezor, self.settings, self.fileMap, self.logger) # # # elif sb == QtGui.QDialogButtonBox.Reset: # # reportLogging("Reset Clicked, quitting now...", logging.DEBUG, -# # "UI", self.settings, self.logger) +# # "UI", self.settings, self.logger, self) def accept(self): """ @@ -244,9 +409,8 @@ def accept(self): """ if self.validate(): reportLogging("Apply was called by user request. Start processing now.", - logging.DEBUG, "GUI IO", self.settings, self.logger) - feedback = processAllFromApply(self, self.trezor, self.settings, self.fileMap, self.logger) # - self.appendDescription(feedback) + logging.DEBUG, "GUI IO", self.settings, self.logger, self) + processAllFromApply(self, self.trezor, self.settings, self.fileMap, self.logger) # # move the cursor to the end of the text, scroll to the bottom cursor = self.textBrowser.textCursor() cursor.setPosition(len(self.textBrowser.toPlainText())) @@ -255,7 +419,7 @@ def accept(self): else: reportLogging("Apply was called by user request. Apply is denied. " "User input is not valid for processing. Did you select a file?", - logging.DEBUG, "GUI IO", self.settings, self.logger) + logging.DEBUG, "GUI IO", self.settings, self.logger, self) # Don't set up a reject() method, it is automatically created. # If created here again it would overwrite the default one @@ -279,7 +443,7 @@ def selectFile(self): qFnameList = dialog.selectedFiles() # QStringList self.fileNames = str(qFnameList.join("")).split("") # convert to Py list reportLogging("Selected files are: %s" % str(self.fileNames), - logging.DEBUG, "GUI IO", self.settings, self.logger) + logging.DEBUG, "GUI IO", self.settings, self.logger, self) self.setSelectedFile(self.fileNames) class EnterPinDialog(QtGui.QDialog, Ui_EnterPinDialog): diff --git a/file_map.py b/file_map.py index 5a9347e..4833ab6 100644 --- a/file_map.py +++ b/file_map.py @@ -1,6 +1,7 @@ import os import os.path import sys +import stat import logging import struct import cPickle @@ -72,6 +73,7 @@ def createDecFile(self, fname): if os.path.isfile(fname): self.logger.warning("File %s exists and decrytion will overwrite it.", fname) if not os.access(fname, os.W_OK): + #os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR ) self.logger.error("File %s cannot be written. " "No write permissions. Skipping it.", fname) return @@ -218,12 +220,13 @@ def saveBlobToEncFile(self, fname, obfuscate, twice): fname += basics.TSFEFILEEXT if os.path.isfile(fname): - self.logger.warning("File %s exists and decrytion will overwrite it.", fname) + self.logger.warning("File %s exists and encryption will overwrite it.", fname) if not os.access(fname, os.W_OK): - self.logger.error("File %s cannot be written. " - "No write permissions. Skipping it.", fname) - raise ValueError("File " + fname + " cannot be written. " - "No write permissions. Skipping it.") + os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR ) + #self.logger.error("File %s cannot be written. " + # "No write permissions. Skipping it.", fname) + #raise IOError("File " + fname + " cannot be written. " + # "No write permissions. Skipping it.") with file(fname, "wb") as f: version = basics.TSFEFILEFORMATVERSION @@ -376,8 +379,8 @@ def encryptOnTrezorDevice(self, blob, keystring): # In order to handle blobs larger than 1023 we junk the blobs encrypted = "" first = True - curr, max = 0, len(blob) / self.MAXUNPADDEDTREZORENCRYPTSIZE splits=[blob[x:x+self.MAXUNPADDEDTREZORENCRYPTSIZE] for x in range(0,len(blob),self.MAXUNPADDEDTREZORENCRYPTSIZE)] + curr, max = 0, len(splits) for junk in splits: padded = Padding(BLOCKSIZE).pad(junk) try: @@ -414,13 +417,13 @@ def decryptOnTrezorDevice(self, encryptedblob, keystring): ukeystring = keystring.decode("utf-8") iv, encryptedblob = encryptedblob[:BLOCKSIZE], encryptedblob[BLOCKSIZE:] # we junk the input, decrypt and reassemble the plaintext - curr, max = 0, len(encryptedblob) / self.MAXPADDEDTREZORENCRYPTSIZE blob = "" first = True self.logger.debug("Press confirm on Trezor device for second level " "file decryption on Trezor device itself (if necessary).") self.logger.debug("Trezor decryption: encrypted-size = %d", len(encryptedblob)) splits=[encryptedblob[x:x+self.MAXPADDEDTREZORENCRYPTSIZE] for x in range(0,len(encryptedblob),self.MAXPADDEDTREZORENCRYPTSIZE)] + curr, max = 0, len(splits) for junk in splits: try: plain = self.trezor.decrypt_keyvalue(Magic.levelTwoNode, diff --git a/processing.py b/processing.py index 711ffd5..5ebe69b 100644 --- a/processing.py +++ b/processing.py @@ -20,25 +20,6 @@ from encoding import q2s, s2q import basics -class Feedback(object): - """ - string reported back to the GUI and displayed in the Status textarea of the GUI - """ - def __init__(self): - self.feedback = "" # html formatted string with
as linebreaks - - def addFeedback(self,fb): - self.feedback += fb - - def setFeedback(self,fb): - self.feedback = fb - - def getFeedback(self): - return self.feedback - - def clearFeedback(self): - self.feedback = "" - class Settings(object): """ Settings, command line options, GUI selected values @@ -46,7 +27,6 @@ class Settings(object): def __init__(self, logger): self.logger = logger - self.guiExists = False self.VArg = False self.HArg = False self.TArg = False @@ -62,7 +42,6 @@ def __init__(self, logger): self.inputFiles = [] # list of input filenames def printSettings(self): - self.logger.debug("self.guiExists = %s", self.guiExists) self.logger.debug("self.VArg = %s", self.VArg) self.logger.debug("self.HArg = %s", self.HArg) self.logger.debug("self.TArg = %s", self.TArg) @@ -81,9 +60,12 @@ def gui2Settings(self, dialog, trezor): trezor.prefillPassphrase(q2s(dialog.pw1())) self.DArg = dialog.dec() self.NArg = dialog.decFn() - self.EArg = dialog.enc() and not dialog.encTwice() and not dialog.encObf() - self.OArg = dialog.encObf() or dialog.encTwiceObf() - self.XArg = dialog.encTwice() or dialog.encTwiceObf() + self.EArg = dialog.enc() + self.MArg = dialog.encFn() + self.OArg = dialog.encObf() + self.XArg = dialog.encTwice() + self.SArg = dialog.encSafe() + self.WArg = dialog.encWipe() or dialog.decWipe() self.PArg = q2s(dialog.pw1()) if self.PArg is None: self.PArg = "" @@ -97,50 +79,70 @@ def settings2Gui(self, dialog, trezor): dialog.setSelectedFiles(self.inputFiles) dialog.setDec(self.DArg) dialog.setDecFn(self.NArg) - dialog.setEnc(self.EArg and not self.OArg and not self.XArg) - dialog.setEncObf(self.OArg and not self.XArg) - dialog.setEncTwice(self.XArg and self.EArg and not self.OArg) - dialog.setEncTwiceObf(self.XArg and self.EArg and self.OArg) + dialog.setDecWipe(self.WArg and self.DArg) + dialog.setEnc(self.EArg) + dialog.setEncFn(self.MArg) + dialog.setEncObf(self.OArg and self.EArg) + dialog.setEncTwice(self.XArg and self.EArg) + dialog.setEncSafe(self.SArg and self.EArg) + dialog.setEncWipe(self.WArg and self.EArg) dialog.setPw1(self.PArg) dialog.setPw2(self.PArg) self.printSettings() -def shred(filename, passes, settings = None, logger = None, feedback = None): +def shred(filename, passes, settings = None, logger = None, dialog = None): """ There is no guarantee that the file will actually be shredded. The OS or the smart disk might buffer it in a cache and data might remain on the physical disk. This is a best effort. """ - if not os.path.isfile(filename): - raise IOError("Cannot shred, \"%s\" is not a file." % filename) - - ld = os.path.getsize(filename) - fh = open(filename, "w") - for _ in range(int(passes)): - data = "0" * ld - fh.write(data) - fh.seek(0, 0) - fh.close() - fh = open(filename, "w") - fh.truncate(0) - fh.close() - urandom_entropy = os.urandom(64) - randomBin = hashlib.sha256(urandom_entropy).digest() - randomB64 = base64.urlsafe_b64encode(randomBin).replace("=", "-") - urandom_entropy = os.urandom(64) - randomBin = hashlib.sha256(urandom_entropy).digest() - randomB64 = randomB64 + base64.urlsafe_b64encode(randomBin).replace("=", "-") - os.rename(filename, randomB64) - os.remove(randomB64) - if settings is not None and logger is not None and feedback is not None: + try: + if not os.path.isfile(filename): + raise IOError("Cannot shred, \"%s\" is not a file." % filename) + + ld = os.path.getsize(filename) + fh = open(filename, "w") + for _ in range(int(passes)): + data = "0" * ld + fh.write(data) + fh.seek(0, 0) + fh.close() + fh = open(filename, "w") + fh.truncate(0) + fh.close() + urandom_entropy = os.urandom(64) + randomBin = hashlib.sha256(urandom_entropy).digest() + randomB64 = base64.urlsafe_b64encode(randomBin).replace("=", "-") + urandom_entropy = os.urandom(64) + randomBin = hashlib.sha256(urandom_entropy).digest() + randomB64 = randomB64 + base64.urlsafe_b64encode(randomBin).replace("=", "-") + os.rename(filename, randomB64) + os.remove(randomB64) + except IOError, e: + if settings is not None and logger is not None and dialog is not None: + reportLogging("Skipping shredding of file \"%s\" (IO error: %s)" % + (filename, e), logging.WARN, + "IO Error", settings, logger, dialog) + elif logger is not None: + logger.warning("Skipping shredding of file \"%s\" (IO error: %s)" % + (filename, e)) + else: + print("Skipping shredding of file \"%s\" (IO error: %s)" % + (filename, e)) + return False + if settings is not None and logger is not None and dialog is not None: reportLogging("File \"%s\" has been shredded and deleted." % filename, - logging.INFO, "File IO", settings, logger, feedback) - + logging.INFO, "File IO", settings, logger, dialog) + elif logger is not None: + logger.info("Info: File \"%s\" has been shredded and deleted." % filename) + else: + print("Info: File \"%s\" has been shredded and deleted." % filename) + return True def usage(): - print """TrezorSymmetricFileEncryption.py [-v] [-h] [-l ] [-t] [-2] [-s] [-w] [-o | -e | -d | -n] [-p ] + print """TrezorSymmetricFileEncryption.py [-v] [-h] [-l ] [-t] [-2] [-s] [-w] [-o | -e | -d | -m | -n] [-p ] -v, --verion print the version number -h, --help @@ -179,9 +181,11 @@ def usage(): only relevant for `-e` and `-o`; ignored in all other cases. Primarily useful for testing. -w, --wipe - shred the plaintext file after encryption; - only relevant for `-e` and `-o`; ignored in all other cases. - Use with caution. May be used together with `-s`. + shred the inputfile after creating the output file + i.e. shred the plaintext file after encryption or + shred the encrypted file after decryption; + only relevant for `-d`, `-e` and `-o`; ignored in all other cases. + Use with extreme caution. May be used together with `-s`. one or multiple files to be encrypted or decrypted @@ -230,6 +234,10 @@ def usage(): # encrypt contract and obfuscate output producing e.g. TQFYqK1nha1IfLy_qBxdGwlGRytelGRJ TrezorSymmetricFileEncryption.py -o contract.doc + # encrypt contract and obfuscate output producing e.g. TQFYqK1nha1IfLy_qBxdGwlGRytelGRJ + # performs safety check and then shreds contract.doc + TrezorSymmetricFileEncryption.py -e -o -s -w contract.doc + # decrypt contract producing contract.doc TrezorSymmetricFileEncryption.py contract.doc.tsfe @@ -238,6 +246,18 @@ def usage(): # shows plaintext name of encrypted file, e.g. contract.doc TrezorSymmetricFileEncryption.py -n TQFYqK1nha1IfLy_qBxdGwlGRytelGRJ + + Keyboard shortcuts of GUI: + Apply, Save: Control-A, Control-S + Cancel, Quit: Esc, Control-Q + Copy to clipboard: Control-C + Version, About: Control-V + Set encrypt operation: Control-E + Set decrypt operation: Control-D + Set obfuscate option: Control-O + Set twice option: Control-2 + Set safety option: Control-T + Set wipe option: Control-W """ def printVersion(): @@ -322,8 +342,8 @@ def parseArgs(argv, settings, logger): sys.exit(2) if settings.DArg or settings.NArg or settings.MArg: settings.XArg = False # X is relevant only when -e or -o is set - if settings.EArg and settings.OArg: - settings.EArg = False # if both E and O are set we default to O + if settings.OArg: + settings.EArg = True # treat O like an extra flag, used in addition if settings.MArg: settings.EArg = False settings.OArg = False @@ -331,32 +351,28 @@ def parseArgs(argv, settings, logger): settings.DArg = False settings.inputFiles = args settings.printSettings() - if settings.WArg: - reportLogging("Warning: The option `--wipe` is set. Plaintext files will " - "be shredded after encryption. Abort if you are uncertain or don't understand.", logging.WARNING, - "Dangerous arguments", settings, logger) -def reportLogging(str, level, title, settings, logger, feedback=None): +def reportLogging(str, level, title, settings, logger, dialog=None): """ Displays string str depending on scenario: a) in terminal mode: thru logger (except if loglevel == NOTSET) - b) in GUI mode and GUI window open: in Status textarea of GUI window - c) in GUI mode but window already closed: thru QMessageBox() + b) in GUI mode and GUI window open: (dialog!=None) in Status textarea of GUI window + c) in GUI mode but window still/already closed: (dialog=None) thru QMessageBox() @param str: string to report/log @param level: log level from DEBUG to CRITICAL @param title: window title text """ - if feedback == None: + if dialog is None: guiExists = False else: - guiExists = settings.guiExists + guiExists = True if level == logging.NOTSET: if settings.TArg: print str # stdout elif guiExists: print str # stdout - feedback.addFeedback("
%s" % (str)) + dialog.appendDescription("
%s" % (str)) else: print str # stdout msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Information, @@ -368,7 +384,7 @@ def reportLogging(str, level, title, settings, logger, feedback=None): elif guiExists: logger.debug(str) if logger.getEffectiveLevel() <= level: - feedback.addFeedback("
Debug: %s" % (str)) + dialog.appendDescription("
Debug: %s" % (str)) else: # don't spam the user with too many pop-ups # For debug, instead of a pop-up we write to stdout @@ -379,7 +395,7 @@ def reportLogging(str, level, title, settings, logger, feedback=None): elif guiExists: logger.info(str) if logger.getEffectiveLevel() <= level: - feedback.addFeedback("
Info: %s" % (str)) + dialog.appendDescription("
Info: %s" % (str)) else: logger.info(str) if logger.getEffectiveLevel() <= level: @@ -392,7 +408,7 @@ def reportLogging(str, level, title, settings, logger, feedback=None): elif guiExists: logger.warning(str) if logger.getEffectiveLevel() <= level: - feedback.addFeedback("
Warning: %s" % (str)) + dialog.appendDescription("
Warning: %s" % (str)) else: logger.warning(str) if logger.getEffectiveLevel() <= level: @@ -405,7 +421,7 @@ def reportLogging(str, level, title, settings, logger, feedback=None): elif guiExists: logger.error(str) if logger.getEffectiveLevel() <= level: - feedback.addFeedback("
Error: %s" % (str)) + dialog.appendDescription("
Error: %s" % (str)) else: logger.error(str) if logger.getEffectiveLevel() <= level: @@ -418,15 +434,21 @@ def reportLogging(str, level, title, settings, logger, feedback=None): elif guiExists: logger.critical(str) if logger.getEffectiveLevel() <= level: - feedback.addFeedback("
Critical: %s" % (str)) + dialog.appendDescription("
Critical: %s" % (str)) else: logger.critical(str) if logger.getEffectiveLevel() <= level: msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Critical, title, "Critical: %s" % (str)) msgBox.exec_() - -def analyzeFilename(inputFile, settings, logger, feedback): + if dialog is not None: + # move the cursor to the end of the text, scroll to the bottom + cursor = dialog.textBrowser.textCursor() + cursor.setPosition(len(dialog.textBrowser.toPlainText())) + dialog.textBrowser.ensureCursorVisible() + dialog.textBrowser.setTextCursor(cursor) + +def analyzeFilename(inputFile, settings, logger, dialog): """ Determine from the input filename if we have to Encrypt or decrypt the file. @@ -448,7 +470,7 @@ def analyzeFilename(inputFile, settings, logger, feedback): @param inputFile: filename """ reportLogging("Analyzing filename %s" % inputFile, logging.DEBUG, - "Debug", settings, logger, feedback) + "Debug", settings, logger, dialog) head, tail = os.path.split(inputFile) if '.' in tail: @@ -462,7 +484,7 @@ def analyzeFilename(inputFile, settings, logger, feedback): else: return("e") -def decryptFileNameOnly(inputFile, settings, fileMap, logger, feedback): +def decryptFileNameOnly(inputFile, settings, fileMap, logger, dialog): """ Decrypt a filename. If it ends with .tsfe then the filename is plain text @@ -471,7 +493,7 @@ def decryptFileNameOnly(inputFile, settings, fileMap, logger, feedback): @param inputFile: filename """ reportLogging("Decrypting filename %s" % inputFile, logging.DEBUG, - "Debug", settings, logger, feedback) + "Debug", settings, logger, dialog) head, tail = os.path.split(inputFile) if tail.endswith(basics.TSFEFILEEXT) or (not ((len(tail) % 16 == 0) and \ @@ -488,20 +510,20 @@ def decryptFileNameOnly(inputFile, settings, fileMap, logger, feedback): phead, ptail = os.path.split(plaintextfname) if not isObfuscated: reportLogging("Plaintext filename/path: \"%s\"" % plaintextfname, - logging.DEBUG, "Filename deobfuscation", settings, logger, feedback) + logging.DEBUG, "Filename deobfuscation", settings, logger, dialog) reportLogging("Filename/path \"%s\" is already in plaintext." % tail, - logging.INFO, "Filename deobfuscation", settings, logger, feedback) + logging.INFO, "Filename deobfuscation", settings, logger, dialog) else: reportLogging("Encrypted filename/path: \"%s\"" % inputFile, - logging.DEBUG, "Filename deobfuscation", settings, logger, feedback) + logging.DEBUG, "Filename deobfuscation", settings, logger, dialog) reportLogging("Plaintext filename/path: \"%s\"" % plaintextfname, - logging.DEBUG, "Filename deobfuscation", settings, logger, feedback) + logging.DEBUG, "Filename deobfuscation", settings, logger, dialog) reportLogging("Plaintext filename of \"%s\" is \"%s\"." % (tail, ptail), logging.NOTSET, - "Filename deobfuscation", settings, logger, feedback) + "Filename deobfuscation", settings, logger, dialog) return plaintextfname -def decryptFile(inputFile, settings, fileMap, logger, feedback): +def decryptFile(inputFile, settings, fileMap, logger, dialog): """ Decrypt a file. If it ends with .tsfe then the filename is plain text @@ -510,19 +532,19 @@ def decryptFile(inputFile, settings, fileMap, logger, feedback): @param inputFile: filename of encrypted file """ reportLogging("Decrypting file %s" % inputFile, logging.DEBUG, - "Debug", settings, logger, feedback) + "Debug", settings, logger, dialog) head, tail = os.path.split(inputFile) if not os.path.isfile(inputFile): reportLogging("File \"%s\" does not exist, is not a proper file, " "or is a directory. Skipping it." % inputFile, logging.ERROR, - "File IO Error", settings, logger, feedback) + "File IO Error", settings, logger, dialog) return else: if not os.access(inputFile, os.R_OK): reportLogging("File \"%s\" cannot be read. No read permissions. " "Skipping it." % inputFile, logging.ERROR, "File IO Error", - settings, logger, feedback) + settings, logger, dialog) return if tail.endswith(basics.TSFEFILEEXT): @@ -535,53 +557,59 @@ def decryptFile(inputFile, settings, fileMap, logger, feedback): if not isEncrypted: reportLogging("File/path seems plaintext: \"%s\"" % inputFile, - logging.DEBUG, "File decryption", settings, logger, feedback) + logging.DEBUG, "File decryption", settings, logger, dialog) reportLogging("File \"%s\" seems to be already in plaintext. " "Decrypting a plaintext file will fail. Skipping file." % tail, - logging.WARNING, "File decryption", settings, logger, feedback) + logging.WARNING, "File decryption", settings, logger, dialog) return None else: outputfname = fileMap.createDecFile(inputFile) + # for safety make decrypted file rw to user only + os.chmod(outputfname, stat.S_IRUSR | stat.S_IWUSR ) ohead, otail = os.path.split(outputfname) reportLogging("Encrypted file/path: \"%s\"" % inputFile, - logging.DEBUG, "File decryption", settings, logger, feedback) + logging.DEBUG, "File decryption", settings, logger, dialog) reportLogging("Decrypted file/path: \"%s\"" % outputfname, - logging.DEBUG, "File decryption", settings, logger, feedback) + logging.DEBUG, "File decryption", settings, logger, dialog) reportLogging("File \"%s\" has been decrypted successfully. " "Decrypted file \"%s\" was produced." % (tail, otail), - logging.NOTSET, "File decryption", settings, logger, feedback) + logging.NOTSET, "File decryption", settings, logger, dialog) + if os.path.isfile(inputFile) and settings.WArg and settings.DArg: + # encrypted files are usually read-only, make rw before shred + os.chmod(inputFile, stat.S_IRUSR | stat.S_IWUSR ) + shred(inputFile, 3, settings, logger, dialog) return outputfname -def encryptFileNameOnly(inputFile, settings, fileMap, logger, feedback): +def encryptFileNameOnly(inputFile, settings, fileMap, logger, dialog): """ Encrypt a filename. Show only what the obfuscated filename would be, without encrypting the file """ reportLogging("Encrypting filename %s" % inputFile, logging.DEBUG, - "Debug", settings, logger, feedback) + "Debug", settings, logger, dialog) head, tail = os.path.split(inputFile) - if analyzeFilename(inputFile, settings, logger, feedback) == "d": + if analyzeFilename(inputFile, settings, logger, dialog) == "d": reportLogging("Filename/path seems decrypted: \"%s\"" % inputFile, - logging.DEBUG, "File decryption", settings, logger, feedback) + logging.DEBUG, "File decryption", settings, logger, dialog) reportLogging("Filename/path \"%s\" looks like an encrypted file. " "Why would you encrypt its filename? This looks strange." % tail, - logging.WARNING, "Filename obfuscation", settings, logger, feedback) + logging.WARNING, "Filename obfuscation", settings, logger, dialog) obfFileName = os.path.join(head, fileMap.obfuscateFilename(tail)) ohead, otail = os.path.split(obfFileName) reportLogging("Plaintext filename/path: \"%s\"" % inputFile, - logging.DEBUG, "Filename obfuscation", settings, logger, feedback) + logging.DEBUG, "Filename obfuscation", settings, logger, dialog) reportLogging("Obfuscated filename/path: \"%s\"" % obfFileName, - logging.DEBUG, "Filename obfuscation", settings, logger, feedback) + logging.DEBUG, "Filename obfuscation", settings, logger, dialog) # Do not modify or remove the next line. # The test harness, the test shell script requires it. reportLogging("Obfuscated filename/path of \"%s\" is \"%s\"." % (tail, otail), - logging.NOTSET, "Filename obfuscation", settings, logger, feedback) + logging.NOTSET, "Filename obfuscation", settings, logger, dialog) return obfFileName -def encryptFile(inputFile, settings, fileMap, obfuscate, twice, logger, feedback): +def encryptFile(inputFile, settings, fileMap, obfuscate, twice, logger, dialog): """ Encrypt a file. if obfuscate == false then keep the output filename in plain text and add .tsfe @@ -592,20 +620,20 @@ def encryptFile(inputFile, settings, fileMap, obfuscate, twice, logger, feedback desired or a plaintext filename (False) """ reportLogging("Encrypting file %s" % inputFile, logging.DEBUG, - "Debug", settings, logger, feedback) + "Debug", settings, logger, dialog) originalFilename = inputFile head, tail = os.path.split(inputFile) if not os.path.isfile(inputFile): reportLogging("File \"%s\" does not exist, is not a proper file, " "or is a directory. Skipping it." % inputFile, logging.ERROR, - "File IO Error", settings, logger, feedback) + "File IO Error", settings, logger, dialog) return else: if not os.access(inputFile, os.R_OK): reportLogging("File \"%s\" cannot be read. No read permissions. " "Skipping it." % inputFile, logging.ERROR, "File IO Error", - settings, logger, feedback) + settings, logger, dialog) return if (os.path.getsize(inputFile) > 8388608) and twice: # 8M+ and -2 option @@ -616,7 +644,7 @@ def encryptFile(inputFile, settings, fileMap, obfuscate, twice, logger, feedback "remove the `-2` or `--twice` option." % (tail, os.path.getsize(inputFile) / 819200), logging.WARNING, "Filename obfuscation", - settings, logger, feedback) # 800K/min + settings, logger, dialog) # 800K/min if tail.endswith(basics.TSFEFILEEXT): isEncrypted = True @@ -628,34 +656,34 @@ def encryptFile(inputFile, settings, fileMap, obfuscate, twice, logger, feedback if isEncrypted: reportLogging("File/path seems encrypted: \"%s\"" % inputFile, - logging.DEBUG, "File encryption", settings, logger, feedback) + logging.DEBUG, "File encryption", settings, logger, dialog) reportLogging("File \"%s\" seems to be encrypted already. " "Are you sure you want to (possibly) encrypt it again?" % tail, - logging.WARNING, "File enncryption", settings, logger, feedback) + logging.WARNING, "File enncryption", settings, logger, dialog) outputfname = fileMap.createEncFile(inputFile, obfuscate, twice) # for safety make encrypted file read-only os.chmod(outputfname, stat.S_IRUSR) ohead, otail = os.path.split(outputfname) reportLogging("Plaintext file/path: \"%s\"" % inputFile, - logging.DEBUG, "File encryption", settings, logger, feedback) + logging.DEBUG, "File encryption", settings, logger, dialog) reportLogging("Encrypted file/path: \"%s\"" % outputfname, - logging.DEBUG, "File encryption", settings, logger, feedback) + logging.DEBUG, "File encryption", settings, logger, dialog) if twice: twicetext = " twice" else: twicetext = "" reportLogging("File \"%s\" has been encrypted successfully%s. Encrypted " "file \"%s\" was produced." % (tail,twicetext,otail), logging.NOTSET, - "File encryption", settings, logger, feedback) + "File encryption", settings, logger, dialog) safe = True if settings.SArg: - safe = safetyCheck(inputFile, outputfname, fileMap, settings, logger, feedback) - if safe and settings.WArg: - shred(inputFile, 3, settings, logger, feedback) + safe = safetyCheck(inputFile, outputfname, fileMap, settings, logger, dialog) + if safe and settings.WArg and settings.EArg: + shred(inputFile, 3, settings, logger, dialog) return outputfname -def safetyCheck(plaintextFname, encryptedFname, fileMap, settings, logger, feedback): +def safetyCheck(plaintextFname, encryptedFname, fileMap, settings, logger, dialog): """ check if previous encryption worked by renaming plaintextFname file to plaintextFname..org @@ -674,24 +702,24 @@ def safetyCheck(plaintextFname, encryptedFname, fileMap, settings, logger, feedb randomB64 = base64.urlsafe_b64encode(randomBin).replace("=", "-") originalFname = plaintextFname + "." + randomB64 + ".orignal" os.rename(plaintextFname, originalFname) - decryptedFname = decryptFile(encryptedFname, settings, fileMap, logger, feedback) + decryptedFname = decryptFile(encryptedFname, settings, fileMap, logger, dialog) aresame = filecmp.cmp(decryptedFname, originalFname, shallow=False) ihead, itail = os.path.split(plaintextFname) ohead, otail = os.path.split(encryptedFname) if aresame: reportLogging("Safety check of file \"%s\" (\"%s\") was successful." % (otail,itail), - logging.INFO, "File encryption", settings, logger, feedback) + logging.INFO, "File encryption", settings, logger, dialog) else: reportLogging("Fatal error: Safety check of file \"%s\" (\"%s\") failed! " "You must inestigate. Encryption was flawed!" % (otail,itail), - logging.CRITICAL, "File encryption", settings, logger, feedback) + logging.CRITICAL, "File encryption", settings, logger, dialog) os.remove(decryptedFname) os.rename(originalFname, plaintextFname) return aresame -def convertFile(inputFile, settings, fileMap, logger, feedback): +def convertFile(inputFile, settings, fileMap, logger, dialog): """ Encrypt or decrypt one file. @@ -699,36 +727,40 @@ def convertFile(inputFile, settings, fileMap, logger, feedback): """ if settings.DArg: # decrypt by choice - decryptFile(inputFile, settings, fileMap, logger, feedback) + decryptFile(inputFile, settings, fileMap, logger, dialog) elif settings.MArg: # encrypt (name only) by choice - encryptFileNameOnly(inputFile, settings, fileMap, logger, feedback) + encryptFileNameOnly(inputFile, settings, fileMap, logger, dialog) elif settings.NArg: # decrypt (name only) by choice - decryptFileNameOnly(inputFile, settings, fileMap, logger, feedback) - elif settings.OArg: + decryptFileNameOnly(inputFile, settings, fileMap, logger, dialog) + elif settings.EArg and settings.OArg: # encrypt and obfuscate by choice - encryptFile(inputFile, settings, fileMap, True, settings.XArg, logger, feedback) - elif settings.EArg: + encryptFile(inputFile, settings, fileMap, True, settings.XArg, logger, dialog) + elif settings.EArg and not settings.OArg: # encrypt by choice - encryptFile(inputFile, settings, fileMap, False, settings.XArg, logger, feedback) + encryptFile(inputFile, settings, fileMap, False, settings.XArg, logger, dialog) else: - hint = analyzeFilename(inputFile, settings, logger, feedback) + hint = analyzeFilename(inputFile, settings, logger, dialog) if hint == "d": # decrypt by default - decryptFile(inputFile, settings, fileMap, logger, feedback) + settings.DArg = True + decryptFile(inputFile, settings, fileMap, logger, dialog) + settings.DArg = False else: # encrypt by default - encryptFile(inputFile, settings, fileMap, False, settings.XArg, logger, feedback) + settings.EArg = True + encryptFile(inputFile, settings, fileMap, False, settings.XArg, logger, dialog) + settings.EArg = False -def doWork(trezor, settings, fileMap, logger, feedback): +def doWork(trezor, settings, fileMap, logger, dialog): reportLogging("Time entering doWork(): %s" % datetime.datetime.now(), - logging.DEBUG, "Debug", settings, logger, feedback) + logging.DEBUG, "Debug", settings, logger, dialog) for inputFile in settings.inputFiles: try: reportLogging("Working on file: %s" % inputFile, - logging.DEBUG, "Debug", settings, logger, feedback) - convertFile(inputFile, settings, fileMap, logger, feedback) + logging.DEBUG, "Debug", settings, logger, dialog) + convertFile(inputFile, settings, fileMap, logger, dialog) except PinException: msgBox = QtGui.QMessageBox(text="Invalid PIN") msgBox.exec_() @@ -736,22 +768,24 @@ def doWork(trezor, settings, fileMap, logger, feedback): except CallException: #button cancel on Trezor, so exit sys.exit(6) + except IOError, e: + reportLogging("IO error: %s" % e, logging.CRITICAL, + "Critical Exception", settings, logger, dialog) + traceback.print_exc() # prints to stderr except Exception, e: - reportLogging(e.message, logging.CRITICAL, - "Critical Exception", settings, logger, feedback) + reportLogging("Critical error: %s" % e, logging.CRITICAL, + "Critical Exception", settings, logger, dialog) traceback.print_exc() # prints to stderr reportLogging("Time leaving doWork(): %s" % datetime.datetime.now(), - logging.DEBUG, "Debug", settings, logger, feedback) + logging.DEBUG, "Debug", settings, logger, dialog) def processAllFromApply(dialog, trezor, settings, fileMap, logger): settings.gui2Settings(dialog,trezor) - feedback = Feedback() reportLogging("Apply button was clicked", - logging.DEBUG, "Debug", settings, logger, feedback) - processAll(trezor, settings, fileMap, logger, feedback) + logging.DEBUG, "Debug", settings, logger, dialog) + processAll(trezor, settings, fileMap, logger, dialog) reportLogging("Apply button was processed, returning to GUI", - logging.DEBUG, "Debug", settings, logger, feedback) - return feedback.getFeedback() + logging.DEBUG, "Debug", settings, logger, dialog) -def processAll(trezor, settings, fileMap, logger, feedback): - doWork(trezor, settings, fileMap, logger, feedback) +def processAll(trezor, settings, fileMap, logger, dialog=None): + doWork(trezor, settings, fileMap, logger, dialog) diff --git a/screenshots/screenshot_TrezorSymmetricFileEncryption_aboutWindow.version04b.png b/screenshots/screenshot_TrezorSymmetricFileEncryption_aboutWindow.version04b.png new file mode 100644 index 0000000..7e9599c Binary files /dev/null and b/screenshots/screenshot_TrezorSymmetricFileEncryption_aboutWindow.version04b.png differ diff --git a/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow1.version04b.png b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow1.version04b.png new file mode 100644 index 0000000..8ec3209 Binary files /dev/null and b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow1.version04b.png differ diff --git a/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow2.version04b.png b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow2.version04b.png new file mode 100644 index 0000000..a85f510 Binary files /dev/null and b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow2.version04b.png differ diff --git a/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow3.version04b.png b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow3.version04b.png new file mode 100644 index 0000000..070820c Binary files /dev/null and b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow3.version04b.png differ diff --git a/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow4.version04b.png b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow4.version04b.png new file mode 100644 index 0000000..a6829a8 Binary files /dev/null and b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow4.version04b.png differ diff --git a/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow5.version04b.png b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow5.version04b.png new file mode 100644 index 0000000..635fb7c Binary files /dev/null and b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow5.version04b.png differ diff --git a/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow6.version04b.png b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow6.version04b.png new file mode 100644 index 0000000..4446398 Binary files /dev/null and b/screenshots/screenshot_TrezorSymmetricFileEncryption_mainWindow6.version04b.png differ diff --git a/singleFileExecutableLinuxCreate.sh b/singleFileExecutableLinuxCreate.sh index e6a99c2..1454d07 100755 --- a/singleFileExecutableLinuxCreate.sh +++ b/singleFileExecutableLinuxCreate.sh @@ -8,4 +8,4 @@ echo "Read: https://mborgerson.com/creating-an-executable-from-a-python-script" su -c "pip install pyinstaller" root pyinstaller --hidden-import pkgutil --windowed --icon=icons/TrezorSymmetricFileEncryption.icon.ico --onefile TrezorSymmetricFileEncryption.py echo "Single file executable is: ./dist/TrezorSymmetricFileEncryption" -ll -h ./dist/TrezorSymmetricFileEncryption +ls -h ./dist/TrezorSymmetricFileEncryption diff --git a/testTrezorSymmetricFileEncryption.sh b/testTrezorSymmetricFileEncryption.sh index 854ace2..6270bdd 100755 --- a/testTrezorSymmetricFileEncryption.sh +++ b/testTrezorSymmetricFileEncryption.sh @@ -1,13 +1,15 @@ #!/bin/bash +passphrase="test" + # outputs to stdout the --help usage message. usage () { echo "${0##*/}: Usage: ${0##*/} [--help] ..." echo "${0##*/}: e.g. ${0##*/} 1K" + echo "${0##*/}: e.g. ${0##*/} 10K" echo "${0##*/}: e.g. ${0##*/} 1M" - echo "${0##*/}: e.g. ${0##*/} 1G" - echo "${0##*/}: e.g. ${0##*/} 2047M # this is the maximum size as it is currently limited to 2G minus a few bytes" echo "${0##*/}: e.g. ${0##*/} 1K 2K 3K" + echo "${0##*/}: Tests larger than 1M will take minutes (0.8M/min)." } if [ $# -eq 1 ]; then @@ -22,7 +24,8 @@ if [ $# -ge 1 ]; then plaintextfilearray=() encryptedfilearray=() rm -f __time_measurements__.txt - echo "Step 1: Preparing all test files, click \"Confirm\" button on Trezor when required." + echo "Note : Watch your Trezor device, click \"Confirm\" button on Trezor when required." + echo "Step 1: Preparing all test files" for size in "$@"; do rm __${size}.img* &> /dev/null fallocate -l ${size} __${size}.x.img @@ -34,53 +37,82 @@ if [ $# -ge 1 ]; then encryptedfilearray+=("__${size}.img.tsfe") done echo "Step 2: Encrypting with: " ./TrezorSymmetricFileEncryption.py -t -e "${plaintextfilearray[@]}" - ./TrezorSymmetricFileEncryption.py -t -e "${plaintextfilearray[@]}" &> /dev/null + ./TrezorSymmetricFileEncryption.py -t -e -p ${passphrase} "${plaintextfilearray[@]}" &> /dev/null echo "Step 3: Encrypting filenames with: " ./TrezorSymmetricFileEncryption.py -t -m "${plaintextfilearray[@]}" # prints lines like his: Obfuscated filename/path of "LICENSE" is "TQFYqK1nha1IfLy_qBxdGwlGRytelGRJ". - ./TrezorSymmetricFileEncryption.py -t -m "${plaintextfilearray[@]}" 2> /dev/null | sed -n 's/.*".*".*"\(.*\)".*/\1/p' > __obfFileNames__.txt + ./TrezorSymmetricFileEncryption.py -t -m -p ${passphrase} "${plaintextfilearray[@]}" 2> /dev/null | sed -n 's/.*".*".*"\(.*\)".*/\1/p' > __obfFileNames__.txt readarray -t obfuscatedfilearray < __obfFileNames__.txt rm __obfFileNames__.txt echo "Step 4: Encrypting and obfuscating files with: " ./TrezorSymmetricFileEncryption.py -t -o "${plaintextfilearray[@]}" - /usr/bin/time -o __time_measurements__.txt -f "%E" -a ./TrezorSymmetricFileEncryption.py -t -o "${plaintextfilearray[@]}" &> /dev/null + /usr/bin/time -o __time_measurements__.txt -f "%E" -a ./TrezorSymmetricFileEncryption.py -t -o -p ${passphrase} "${plaintextfilearray[@]}" &> /dev/null for size in "$@"; do mv __${size}.img __${size}.img.org done echo "Step 5: Decrypting files with: " ./TrezorSymmetricFileEncryption.py -t -d "${encryptedfilearray[@]}" - ./TrezorSymmetricFileEncryption.py -t -d "${encryptedfilearray[@]}" &> /dev/null + ./TrezorSymmetricFileEncryption.py -t -d -p ${passphrase} "${encryptedfilearray[@]}" &> /dev/null echo "Step 6: Comparing original files with en+decrypted files with plaintext filenames" for size in "$@"; do diff __${size}.img __${size}.img.org rm __${size}.img done echo "Step 7: Decrypting and deobfuscating files with: " ./TrezorSymmetricFileEncryption.py -t -d "${obfuscatedfilearray[@]}" - /usr/bin/time -o __time_measurements__.txt -f "%E" -a ./TrezorSymmetricFileEncryption.py -t -d "${obfuscatedfilearray[@]}" &> /dev/null + /usr/bin/time -o __time_measurements__.txt -f "%E" -a ./TrezorSymmetricFileEncryption.py -t -d -p ${passphrase} "${obfuscatedfilearray[@]}" &> /dev/null echo "Step 8: Comparing original files with en+decrypted files with obfuscated filenames" for size in "$@"; do diff __${size}.img __${size}.img.org rm __${size}.img.org done for obffile in "${obfuscatedfilearray[@]}"; do - rm "$obffile" + rm -f "$obffile" done echo "Step 9: Encrypting and obfuscating files with 2-level-encryption: " ./TrezorSymmetricFileEncryption.py -t -o -2 "${plaintextfilearray[@]}" - /usr/bin/time -o __time_measurements__.txt -f "%E" -a ./TrezorSymmetricFileEncryption.py -t -o -2 "${plaintextfilearray[@]}" &> /dev/null + /usr/bin/time -o __time_measurements__.txt -f "%E" -a ./TrezorSymmetricFileEncryption.py -t -o -2 -p ${passphrase} "${plaintextfilearray[@]}" &> /dev/null for size in "$@"; do mv __${size}.img __${size}.img.org done echo "Step 10: Decrypting and deobfuscating files with 2-level-encryption: " ./TrezorSymmetricFileEncryption.py -t -d "${obfuscatedfilearray[@]}" - /usr/bin/time -o __time_measurements__.txt -f "%E" -a ./TrezorSymmetricFileEncryption.py -t -d "${obfuscatedfilearray[@]}" &> /dev/null + /usr/bin/time -o __time_measurements__.txt -f "%E" -a ./TrezorSymmetricFileEncryption.py -t -d -p ${passphrase} "${obfuscatedfilearray[@]}" &> /dev/null echo "Step 11: Comparing original files with en+decrypted files with obfuscated filenames" for size in "$@"; do diff __${size}.img __${size}.img.org rm __${size}.img - rm __${size}.img.org - rm __${size}.img.tsfe + done + echo "Step 11: Encrypting with safety check and wipe: " ./TrezorSymmetricFileEncryption.py -t -e -s -w -p ${passphrase} "${plaintextfilearray[@]}" + ./TrezorSymmetricFileEncryption.py -t -e -s -w -p ${passphrase} "${plaintextfilearray[@]}" &> /dev/null + for size in "$@"; do + ls __${size}.img 2> /dev/null # file should not exist + done + echo "Step 12: Decrypting with safety check and wipe: " ./TrezorSymmetricFileEncryption.py -t -d -s -w -p ${passphrase} "${encryptedfilearray[@]}" + ./TrezorSymmetricFileEncryption.py -t -d -s -w -p ${passphrase} "${encryptedfilearray[@]}" &> /dev/null + for size in "$@"; do + ls __${size}.img.tsfe 2> /dev/null # file should not exist + diff __${size}.img __${size}.img.org + done + echo "Step 13: Encrypting with obfuscation, safety check and wipe: " ./TrezorSymmetricFileEncryption.py -t -e -o -s -w -p ${passphrase} "${plaintextfilearray[@]}" + ./TrezorSymmetricFileEncryption.py -t -e -o -s -w -p ${passphrase} "${plaintextfilearray[@]}" &> /dev/null + for size in "$@"; do + ls __${size}.img 2> /dev/null # file should not exist + done + echo "Step 14: Decrypting with safety check and wipe: " ./TrezorSymmetricFileEncryption.py -t -d -s -w -p ${passphrase} "${obfuscatedfilearray[@]}" + ./TrezorSymmetricFileEncryption.py -t -d -s -w -p ${passphrase} "${obfuscatedfilearray[@]}" &> /dev/null + for size in "$@"; do + diff __${size}.img __${size}.img.org + done + for obffile in "${obfuscatedfilearray[@]}"; do + ls "$obffile" 2> /dev/null # file should not exist + done + for size in "$@"; do + rm -f __${size}.img + rm -f __${size}.img.org + rm -f __${size}.img.tsfe done for obffile in "${obfuscatedfilearray[@]}"; do - rm "$obffile" + rm -f "$obffile" done echo "End : If no warnings or errors were echoed, then there were no errors, all tests terminated successfully." else # zero arguments, we run preset default test cases - ${0} 1K 2K 3K 4K 5K 6K 7K 8K 9K 10K 1M 2M 3M + echo "Note : This default test will take about 3-4 minutes." + echo "Note : If you have a PIN set, you will have to enter it about 10 times. Consider disabling it." + ${0} 1K 10K 100K 1M fi