pax_global_header00006660000000000000000000000064142034307010014504gustar00rootroot0000000000000052 comment=73eac2a7708d4f2b04ccd911db01a4dcda2e27ec luckyLUKS-2.1.0/000077500000000000000000000000001420343070100133325ustar00rootroot00000000000000luckyLUKS-2.1.0/.gitignore000066400000000000000000000014071420343070100153240ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ dist_deb/ deb_dist/ downloads/ eggs/ lib/ lib64/ parts/ sdist/ var/ README luckyluks.1.gz *.egg-info/ .installed.cfg *.egg *.eggs # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo #*.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ #Pydev/Eclipse .project .pydevproject .settings luckyLUKS-2.1.0/CHANGELOG000066400000000000000000000134401420343070100145460ustar00rootroot00000000000000luckyluks (2.1.0) unstable; urgency=low * Add quickformat to initialize container with fallocate instead of filling with random data -- Jasper van Hoorn Wed, 16 Feb 2022 23:48:23 +0100 luckyluks (2.0.1) unstable; urgency=low * Update readme/manpages -- Jasper van Hoorn Tue, 25 Jan 2022 01:03:37 +0100 luckyluks (2.0.0) unstable; urgency=low * bugfix tcplay keyfile only * testing: kde, (ubuntu-) gnome, xfce, lxqt, cinnamon, mate -- Jasper van Hoorn Tue, 25 Jan 2022 00:23:45 +0100 luckyluks (1.99.0) unstable; urgency=low * minor adjustments for LUKS2 support * cleanup: remove legacy code for cryptsetup < 2.0 * cleanup: remove legacy support for qt4/python2 * update build scripts * update/cleanup readme -- Jasper van Hoorn Mon, 17 Jan 2022 16:42:21 +0100 luckyluks (1.2.0) unstable; urgency=low * minor adjustments for gtk2/3 version (feature complete alternative to this qt based version) * bugfixes error handling -- Jasper van Hoorn Tue, 04 May 2015 16:32:28 +0100 luckyluks (1.1.0) unstable; urgency=low * keyfile support (open/create for LUKS and TrueCrypt containers) * generate safe keyfile from ui * improved help dialogs -- Jasper van Hoorn Sun, 19 Apr 2015 22:16:24 +0100 luckyluks (1.0.5) unstable; urgency=low * fix locale transfer to worker process -- Jasper van Hoorn Tue, 31 Mar 2015 19:18:46 +0100 luckyluks (1.0.4) unstable; urgency=low * degrade gracefully if no system tray available -- Jasper van Hoorn Mon, 30 Mar 2015 18:26:39 +0100 luckyluks (1.0.3) unstable; urgency=low * improved debian packaging -- Jasper van Hoorn Fri, 27 Mar 2015 13:49:53 +0100 luckyluks (1.0.2) unstable; urgency=low * removed dependency on pkg_resources * improved error passing from worker to UI -- Jasper van Hoorn Thu, 26 Mar 2015 23:11:28 +0100 luckyluks (1.0.1) unstable; urgency=low * bugfixes sudo setup & losetup (closes https://github.com/jas-per/luckyLUKS/issues/4) * use nosuid/nodev for mount (thanks to https://github.com/mhogomchungu) * modified access rights checks -- Jasper van Hoorn Fri, 13 Mar 2015 19:16:52 +0100 luckyluks (1.0.0) unstable; urgency=low * don't require tcplay to just open TrueCrypt containers * fallback icon for dialog-password * workaround for .desktop file creation * wait before detaching loopback device (workaround udisks-daemon crashes) * bugfixes python3 * fully tested on Ubuntu 14.04 & 12.04 (Unity, KDE, XFCE, LXDE) / Kubuntu 15.04-beta1 / Debian wheezy & jessie & unstable / Tails -- Jasper van Hoorn Sun, 08 Mar 2015 23:56:05 +0100 luckyluks (0.9.10) unstable; urgency=low * full qt5 support (python3 version uses pyqt5 if installed) * packaging fixes -- Jasper van Hoorn Sat, 07 Mar 2015 08:44:23 +0100 luckyluks (0.9.9) unstable; urgency=low * Create TrueCrypt containers * Simplyfy interface / expandable advanced settings * workaround udisks-daemon crash (manual loopback device handling) * Restructured "Help" dialogs * Toggle hidden passphrase input when creating container * FAQ & Translation notes added to Readme -- Jasper van Hoorn Tue, 03 Mar 2015 18:41:30 +0100 luckyluks (0.9.8) unstable; urgency=low * python3 packaging (make needs patched stdeb: https://github.com/astraw/stdeb/pull/93) * minor qt5 compatibility changes -- Jasper van Hoorn Wed, 11 Feb 2015 19:15:53 +0100 luckyluks (0.9.7) unstable; urgency=low * better cleanup: detect input pipe close to exit worker process -- Jasper van Hoorn Fri, 06 Feb 2015 21:53:41 +0100 luckyluks (0.9.6) unstable; urgency=low * add show/hide to systray context menu (required for unity) * setup sudo if not configured via su * add *sbin/ to search path for tool checks * refactor util methods * Makefile and setup.py for debian packaging * tested on debian sid, ubuntu 14.04 and kubuntu 12.04 & 14.04 -- Jasper van Hoorn Fri, 30 Jan 2015 15:02:11 +0100 luckyluks (0.9.5) unstable; urgency=low * using poll instead of epoll to connect with sudo process to avoid problems on python<2.7.4 * check dmsetup version for unicode/special character support * fixes unicode output on exception display -- Jasper van Hoorn Thu, 22 Jan 2015 17:48:23 +0100 luckyluks (0.9.4) unstable; urgency=low * fixes build failure ubuntu ppa because of setup_requires -- Jasper van Hoorn Tue, 20 Jan 2015 17:32:05 +0100 luckyluks (0.9.3) unstable; urgency=low * modified gettext & qt application init * more reliable parsing of sudo output * use xdg-desktop-menu to add menuentry when available * use python to set uid/gui of partition root on create (instead of using mkfs which fails in older versions of mkfs) -- Jasper van Hoorn Thu, 15 Jan 2015 22:51:38 +0100 luckyluks (0.9.2) unstable; urgency=low * first try debian packaging with ubuntu ppa -- Jasper van Hoorn Sat, 27 Dec 2014 00:54:45 +0100 luckyluks (0.9.1) unstable; urgency=low * Support for internationalization * German localization * zip-'packaging' -- Jasper van Hoorn Sat, 20 Dec 2014 23:53:49 +0100 luckyluks (0.9.0) unstable; urgency=low * Initial version, finally getting things in shape for a public release Been using the program for almost half a year and shared it with some friends as well, so major bugs should hopefully be squashed by now. -- Jasper van Hoorn Fri, 05 Dec 2014 11:37:23 +0100 luckyLUKS-2.1.0/LICENSE000066400000000000000000000016461420343070100143460ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Files: * Copyright: (c) 2014,2015,2022 Jasper van Hoorn License: GPL-3+ This program 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. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . . On Debian systems, the full text of the GNU General Public License version 3 can be found in the file `/usr/share/common-licenses/GPL-3`.luckyLUKS-2.1.0/MANIFEST.in000066400000000000000000000001411420343070100150640ustar00rootroot00000000000000include luckyluks.desktop include luckyluks.1.gz include luckyluks include LICENSE include READMEluckyLUKS-2.1.0/Makefile000066400000000000000000000107231420343070100147750ustar00rootroot00000000000000PYTHON=python3 NAME=$(shell ${PYTHON} setup.py --name) LOWER_NAME = $(shell echo $(NAME) | tr A-Z a-z) VERSION=$(shell ${PYTHON} setup.py --version) SDIST=dist/${NAME}-${VERSION}.tar.gz VENV=/tmp/venv .PHONY: dist_zip dist_deb # requires: python3-setuptools, python3-stdeb, python3-babel, help2man, pandoc -> TODO: move into setup.cfg/build-dep dist: clean compile_locales manpage readme ${PYTHON} setup.py sdist @echo "Signing tgz file" gpg -ba -o dist/${NAME}-${VERSION}.tar.gz.asc dist/${NAME}-${VERSION}.tar.gz # requires stdeb >= 0.8.5 dist_deb: dist mkdir dist_deb cp dist/${NAME}-${VERSION}.tar.gz dist_deb/${LOWER_NAME}_${VERSION}.orig.tar.gz cp dist/${NAME}-${VERSION}.tar.gz.asc dist_deb/${LOWER_NAME}_${VERSION}.orig.tar.gz.asc cd dist_deb && tar -xvzf ${LOWER_NAME}_${VERSION}.orig.tar.gz cd dist_deb/${NAME}-${VERSION} && ${PYTHON} setup.py --command-packages=stdeb.command debianize --extra-cfg-file setup.cfg cp CHANGELOG dist_deb/${NAME}-${VERSION}/debian/changelog echo 'README' >> dist_deb/${NAME}-${VERSION}/debian/${LOWER_NAME}.docs echo 'luckyluks.1.gz' >> dist_deb/${NAME}-${VERSION}/debian/${LOWER_NAME}.manpages echo 'luckyluks usr/bin' >> dist_deb/${NAME}-${VERSION}/debian/${LOWER_NAME}.install mkdir dist_deb/${NAME}-${VERSION}/debian/upstream/ cp signing-key.asc dist_deb/${NAME}-${VERSION}/debian/upstream/ echo 'version=3\nopts=filenamemangle=s/.+\/v?(\d?\S*)\.tar\.gz/luckyluks_$$1.tar.gz/,pgpsigurlmangle=s/archive\/v?(\d\S*)\.tar\.gz/releases\/download\/v$$1\/v$$1.tar.gz.asc/ https://github.com/jas-per/luckyLUKS/releases .*/archive/v(\d?\S*)\.tar\.gz' >> dist_deb/${NAME}-${VERSION}/debian/watch echo 'override_dh_install:' >> dist_deb/${NAME}-${VERSION}/debian/rules echo '\tdh_install --sourcedir=./' >> dist_deb/${NAME}-${VERSION}/debian/rules sed -e "s/7/9/g" -i dist_deb/${NAME}-${VERSION}/debian/compat sed -e "s,Standards-Version: 3.9.1,Standards-Version: 4.5.0\nVcs-Git: git://github.com/jas-per/luckyLUKS.git\nVcs-Browser: https://github.com/jas-per/luckyLUKS,g" -i dist_deb/${NAME}-${VERSION}/debian/control cd dist_deb/${NAME}-${VERSION} && debuild -S -sa dist_zip: mkdir -p dist_zip rm -f dist_zip/${NAME}-${VERSION} zip -r ${NAME}-${VERSION} ${NAME}/ __main__.py -i \*.py \*.mo echo '#!/usr/bin/env ${PYTHON}' | cat - ${NAME}-${VERSION} > temp && mv temp ${NAME}-${VERSION} chmod +x ${NAME}-${VERSION} mv ${NAME}-${VERSION} dist_zip/ # these would work if stdeb could handle additional files (manpage etc) && custom changelog # use dist_deb target instead and build binary package manually if needed #deb_src: clean manpage # ${PYTHON} setup.py --command-packages=stdeb.command sdist_dsc --extra-cfg-file setup.cfg # debsign dist_deb/${LOWER_NAME}_${VERSION}*_source.changes #deb_bin: deb_src # cd dist_deb/${NAME}-${VERSION} && debuild -us -uc update_locales: ${PYTHON} setup.py extract_messages --output-file ${NAME}/locale/${NAME}.pot ${PYTHON} setup.py update_catalog --domain ${NAME} --input-file ${NAME}/locale/${NAME}.pot --output-dir ${NAME}/locale compile_locales: ${PYTHON} setup.py compile_catalog --domain ${NAME} --directory ${NAME}/locale init_locale: if test -z "$$NEW_LANG";\ then echo 'please provide a language eg. `make init_locale NEW_LANG="LANGCODE"`';\ else ${PYTHON} setup.py init_catalog -l ${NEW_LANG} -i ${NAME}/locale/${NAME}.pot -d ${NAME}/locale; fi; manpage: help2man -n 'GUI for creating and unlocking LUKS/TrueCrypt volumes from container files' -N --no-discard-stderr ./luckyluks | gzip -9 > luckyluks.1.gz readme: sed '/Installation/,/repository tools./d' README.rst | pandoc -r rst -w plain -o README install: ${PYTHON} setup.py install --install-layout=deb check: @echo '### pylint check ###' find . -name \*.py | grep -v "^test_" | xargs pylint --max-line-length=120 --max-args=7 --disable=invalid-name,unused-argument,fixme,import-outside-toplevel,no-self-use --errors-only --additional-builtins=_ --extension-pkg-whitelist=PyQt5 --reports=n @echo '### pep8 check ###' pep8 *.py ./luckyLUKS --max-line-length=120 --ignore=E731,W503,W504 # autopep8 ./luckyLUKS/*.py --in-place --verbose --ignore=E501,E731,W503,W504 #deploy: # # make sdist # rm -rf dist # python setup.py sdist # # # setup venv # rm -rf $(VENV) # virtualenv --no-site-packages $(VENV) # $(VENV)/bin/pip install $(SDIST) #upload: # ${PYTHON} setup.py sdist register upload clean: ${PYTHON} setup.py clean rm -rf build/ dist build ${NAME}-${VERSION} ${NAME}.egg-info deb_dist dist_zip dist_deb debian luckyluks.1.gz README find . -name '*.pyc' -delete luckyLUKS-2.1.0/README.rst000066400000000000000000000436201420343070100150260ustar00rootroot00000000000000luckyLUKS ========= luckyLUKS is a Linux GUI for creating and (un-)locking encrypted volumes from container files. Unlocked containers leave an icon in the systray \ as a reminder to close them eventually ;) Supports cryptsetup/LUKS and Truecrypt container files. luckyLUKS was brought to life to offer an equivalent to the Windows TrueCrypt application. Although most Linux distributions provide excellent support for \ encrypted partitions these days - you can choose to run from a completely encrypted harddrive on installation with one or two clicks - the situation with \ encrypted containers is not that great. An encrypted container is basically a large file which encapsulates an encrypted partition. This approach has some advantages, especially for casual computer users: - No need to deal with partition table wizardry when creating an encrypted container, you basically create a file on a harddrive, it doesn't matter if its an internal one or an external usbstick etc.. - Backup is straightforward as well, just copy the file somewhere else - done! No need to backup your precious data unencrypted - Share confidential information by copying the container file. Similar to gpg encrypted archives but easier to handle (unlock - view or modify data - lock again) - You can easily add some encrypted private data to an unencrypted external harddrive you want to share with friends or take with you while travelling - Lots of users are already quite familiar with all this, because their first touch with data encryption has been TrueCrypt which uses the encrypted container approach luckyLUKS follows a keep-it-simple philosophy that aims to keep users from shooting themselves in the foot and might be a bit too simple for power users - \ please use `ZuluCrypt `_ and/or `cryptsetup `_/`tcplay `_ on the command line \ if you need special options when creating new containers. On the other hand, to unlock existing containers luckyLUKS offers all you need and the possibility \ to create a shortcut to a container in your start menu or on the desktop. From the shortcut its just one click and you can enter your password to \ unlock the container. For technical details please see the FAQ at the end of this page. For a first impression: .. image:: https://github.com/jas-per/luckyLUKS/blob/gh-pages/screencast.gif :align: center :alt: screencast of luckyLUKS Installation ============ Since 2022 luckyLUKS is available in Debian/Ubuntu based distributions - just use your package manager to install. \ For older Ubuntu and derivates use this `ppa `_:: > sudo add-apt-repository ppa:jas-per/lucky-luks > sudo apt-get update && sudo apt-get upgrade > sudo apt-get install luckyluks (For Ubuntu LTS <20.04 install :code:`python-luckyLUKS` or :code:`python3-luckyLUKS` still present in that ppa) Alternatively Debian based distributions can use this Debian package and install manually: `luckyluks_2.1.0_all.deb `_ On other distriubutions you can use the following zip-packaged python file: `luckyLUKS-2.1.0 `_ This file contains all resources and can be executed directly by the python intepreter. Place in :code:`/usr/bin` and change ownership to root:: > sudo mv luckyLUKS-2.1.0 /usr/bin/ > sudo chown root:root /usr/bin/luckyLUKS-2.1.0 > sudo chmod 755 /usr/bin/luckyLUKS-2.1.0 Then start with :code:`luckyLUKS-2.1.0` on the command line or create a desktop shortcut manually. Dependencies ------------ To run luckyLUKS, make sure you have the following installed: - :code:`python3` - :code:`cryptsetup` - :code:`sudo` - :code:`python3-pyqt5` - :code:`tcplay` (if you want to create TrueCrypt containers) When using the ubuntu-ppa these will get installed automatically, if you use the deb-/zip-package \ please install the dependencies manually with your distributions repository tools. Desktop environments / distributions ------------------------------------ luckyLUKS gets tested with the major desktop environments: - :code:`gnome` (needs extension for `tray icons `_) - :code:`kde` - :code:`ubuntu gnome` - :code:`xfce` - :code:`cinnamon` - :code:`mate` - :code:`lxqt` There are also some distribution specifics with Debian: - since debian doesn't rely on sudo you have to manually add your user to the sudo group:: > su > usermod -aG sudo USERNAME - dev-mapper is not configured to do automatic mounts on some desktop environments:: please use the 'mount point' option in luckyLUKS Using luckyLUKS with a wayland-based display server / compositor instead of Xorg is possible eg with `gnome`, `kde` or `sway` \ and for security reasons this is very much recommended! There is still some work left to get wayland running smooth though, \ so check usability for yourself - things like gaming, input drivers, screen recording and also tray icon functionality \ might stop you from using a wayland compositor yet. FAQ === luckyLUKS is basically a GUI wrapper for two command line tools: `cryptsetup` and `tcplay`. The cryptsetup project has an excellent `FAQ `_ that explains the underlying cryptography and security in great detail. \ If you want to know more e.g. about choosing a secure password or further protecting your computer, please read the cryptsetup FAQ first. The following \ information mainly refers to questions specific to encrypted containers and luckyLUKS as a graphical interface to cryptsetup and tcplay. Backup ------ There is a whole chapter in the cryptsetup FAQ dealing with backup details. This is because cryptsetup is normally used for encrypted partitions, which complicates things a bit. Since luckyLUKS uses encrypted containers, backup is rather straightforward - just copy the whole container and you're done. \ By copying you technically create a clone of the encrypted LUKS container - see section 6.15 in the cryptsetup `FAQ `_ in case you would like to change your passphrase later on. Key files --------- A key file can be used to allow access to an encrypted container instead of a password. Using a key file resembles unlocking a door with a key in the real world - anyone with access to the key file can open your encrypted container. Make sure to store it at a protected location. \ Its okay to store it on your computer if you are using a digital keystore or an already encrypted harddrive that you unlock on startup with a password. Having the key file on a `small USB drive `_ attached to your real chain of keys \ would be an option as well. Since you don't have to enter a password, using a key file can be a convenient way to access your encrypted container. Just make sure you don't lose the key (file) - backup to a safe location separate from the encrypted container. Printing the raw data \ (use a hex-editor/viewer) to paper is fine as a last resort as well. Although basically any file could be used as a key file, a file with predictable content leads to similar problems as using weak passwords. Audio files or pictures are a good choice. If unsure use the 'create key file' function in luckyLUKS to generate a small key file filled with random data. With LUKS it is also possible to use both, a passphrase and a keyfile. LUKS uses a concept called 'keyslots' that enables up to 8 keys to be used exchangeably to unlock a container. You could use a keyfile to unlock a container on an external drive when using your own computer with an already encrypted system, \ and a passphrase to open the same container on a different computer or in case you lost the keyfile. Because it might be a bit confusing for casual users, this option is not provided in the graphical interface of luckyLUKS. If you want to use it, you have to do the following once on the command line: - generate a new keyfile with luckyLUKS - open the container with luckyLUKS - check which loopback device is used: :code:`sudo losetup -a` - view the LUKS keyslots of this container: :code:`sudo cryptsetup luksDump /dev/loopX` - add the keyfile to the keyslots: :code:`sudo cryptsetup luksAddKey /dev/loopX /PATH/TO/KEYFILE` - view the LUKS keyslots again and you will see another keyslot in use: :code:`sudo cryptsetup luksDump /dev/loopX` After you did this once, you can use the GUI of luckyLUKS, to open the container with either passphrase or keyfile and generate shortcuts for the startup menu as needed. The TrueCrypt format offers another possibility when using keyfiles, where you have to provide both keyfile and password to unlock a container. While this provides a nice `two factor authentication `_ it is also a more advanced approach \ that is beyond the scope of luckyLUKS - please use `ZuluCrypt `_ or the command line for this. And be aware that security through obscurity might not be the right approach for your privacy needs: a weak password combined with a keyfile \ is easily broken if the keyfile gets into the wrong hands. Sudo Access ----------- On Linux encrypted containers get mounted as loopback devices by using the device mapper infrastructure. Access to /dev/mapper is restricted to root for good reason: besides managing encrypted containers, the device mapper is also used by the Logical Volume Manager (LVM) and Software RAIDs for example. \ There have been `ideas `_ on how to allow device-mapper access without root privileges but its complicated - the device mapper developers seem to prefer controlling loopback device mounts by integrating cryptsetup into udisks/dbus/udev/policykit/systemd. \ While this approach can enable fine grained access control in userspace, it also complicates things quite substantially - nowadays it might be possible to use encrypted containers this way, but decent documentation is hard to find. So for now accessing the device mapper directly with administrative privileges is needed to use encrypted containers. Almost every Unix systems offers two ways to do this: setuid and sudo. With `setuid `_ an executable gains elevated privileges directly, \ while `sudo `_ is a program used to give elevated privileges to other executables, that can be configured to allow fine grained access control in userspace similar to the policykit framework mentioned above. With both setuid and sudo, \ it is the application developer's responsibility to take great care that the program running with elevated privileges cannot be used in any malicious way. \ Popular methods for privilege escalation in this context are buffer overruns, unsanitized environments, shell injection or toctou-attacks. Because running setuid executables does not require an additional password, setuid is generally considered a security risk and to be avoided whenever possible. There are usually very few (well reviewed) setuid binaries on a modern Linux system. Sudo on the other hand requires the user's password, \ has a long record of security-conscious development and lots of flexibility in its access control \ (e.g.. the *Ubuntu distributions or Apples OSX rely heavily on using sudo for administrative tasks). luckyLUKS uses sudo for all privileged operations and also offers the option to create a sudo-rule to allow the current user to omit their password for running luckyLUKS. The last remark on elevated privileges is about luckyLUKS graphical user interface. To minimize the possible attack surface, all UI code is run with normal user rights, while all privileged operations are executed in separate helper processes (privilege separation). Is my data/passphrase safe? --------------------------- This depends more on general computer security issues than on this particular application. In times where you cannot even trust your `hard drive `_ you have to go a long way to be at least reasonably safe from state-level attackers. \ If this is a requirement for you, consider using a readonly operating system like `Tails `_ and keep learning about computer security. Sad to say, but a GUI to unlock your encrypted data should be the least of your concerns. OK, but what about the safety of my passphrase in luckyLUKS compared to using cryptsetup/tcplay directly in a terminal? There are two areas that might be problematic: The first is the standard window system on Unix called X. The X window system originates in a time where the requirements \ and possibilities of a graphical interface where quite different from what they are now. The security architecture is fundamentally broken from todays point of view. It is for instance not possible to keep other applications from receiving all key-events - which includes the passphrase in our case \ (keep in mind that this is also true when using cryptsetup in an X-windowed terminal). That said, the successor to X called Wayland is just around the corner, if you feel adventurous try using luckyLUKS in a Wayland based compositor today. The second problem is about keeping the passphrase in memory. In general you `should `_ trust your operating system to restrict memory access. Nevertheless it is good practice to overwrite the data in memory \ as soon as unneeded while handling sensitive information. Since luckyLUKS is written in Python, direct memory access is not possible, only removing all references to the passphrase and wait for the garbage collection to clean up later. This it not a problem per-se, since you have to trust your operating system anyway, \ but can turn into a security issue when the memory content gets written to disk on hibernation or into the swapfile. When this happens any sensitive data could still be found in clear text even weeks after the computer was shut down. \ Easy solution: use `encrypted swap `_! And consider using full disk encryption, to make sure nobody with physical access to your computer can e.g.. add a keylogger on startup. OK, so whats the bottom line? LUKS or TrueCrypt containers are safe, nobody that gets access to such a container of yours will be able to open it without your passphrase. The vulnerable point is the computer you use to access the encrypted data. The degree of vulnerability depends on the resources \ and determination of an attacker. Furthermore safety is relative to your own needs being a tradeoff between comfort and security. Using luckyLUKS on your daily operating system without any further precautions will still protect your private data against almost all those prying eyes. \ If you want more certainty use full disk encryption, a live operating system like :code:`Tails` or a computer permanently disconnected from the internet in that order. Accessing containers on Windows ------------------------------- If you want to access encrypted containers on Linux and Windows, use NTFS as the filesystem inside the container. It is the only modern filesystem available on Windows and can be used from Linux as well. Since access permissions cannot be mapped from NTFS to Linux user accounts, \ access to NTFS devices is often not restricted -> take care when using unlocked NTFS devices in a multiuser environment! If you share a computer with other people like family members, always close your encrypted container before switching sessions. To access LUKS containers from Windows use `LibreCrypt `_. To access TrueCrypt containers use the original TrueCrypt or a successor like `VeraCrypt `_. Translations ============ The user interface of luckyLUKS is fully translateable, and to offer more translations your help is needed. Since the application is not too complex and more or less feature complete at this point, it won't take long to translate all the neccessary strings and translating won't be an ongoing effort. - install a translations editor (eg `Poedit `_) and `python-babel `_ - `Download `_ the source code of luckyLUKS - Open a terminal, change directory to the location of the luckyLUKS source files - Create new locale file (eg :code:`make init_locale NEW_LANG="pt"` for Portuguese, see two-letter codes `here `_) - You will find the new locale file in :code:`luckyLUKS/locale//LC_MESSAGES/luckyLUKS.po` - Edit this file in the translations editor - After editing the po file has to be compiled. Poedit can do this automatically: go to :code:`Preferences` and check :code:`Automatically compile .mo file on save`. Or use :code:`make compile_locales` from the source directory. - To test your translation, start luckyLUKS from the command line. You might have to set the locale explicitly, if your operation system is using a different locale (eg :code:`LANG=pt_PT.utf-8 LANGUAGE=pt ./luckyluks`) When you are happy with the results, mail the .po-file you created and your translation will get included in the next release. Pull requests are welcome too :) Bugs ==== Please report all bugs on the github `issue tracker `_. Since this is a GUI tool, the most important information is the exact name of the distribution including the version/year \ and the desktop environment used (eg Gnome, KDE, Mate, XFCE, LXDE). This will help reproducing bugs on a virtual machine a lot. luckyLUKS-2.1.0/__main__.py000066400000000000000000000040211420343070100154210ustar00rootroot00000000000000""" luckyLUKS is a GUI for creating and (un-)locking LUKS volumes from container files. For more information visit: http://github.com/jas-per/luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) This program 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. This program 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. """ import locale import pkgutil import io from gettext import GNUTranslations, NullTranslations from luckyLUKS import main # This is the entry point, when run from the zip-file package. # To access resources inside the zip file, pkgutil.get_data() has to be used. # Because of this, gettext will be initialized here, # to search for a .mo file for the users locale inside the zip if __name__ == '__main__': locale.setlocale(locale.LC_ALL, '') loc, enc = locale.getlocale(locale.LC_MESSAGES) l10n_resource = None # try to find the corresponding gettext file (*.mo) for the users locale in the zip file if loc is not None and loc != 'C': try: l10n_resource = pkgutil.get_data( 'luckyLUKS', 'locale/{0}/LC_MESSAGES/luckyLUKS.mo'.format(loc) ) except IOError: if '_' in loc: try: l10n_resource = pkgutil.get_data( 'luckyLUKS', 'locale/{0}/LC_MESSAGES/luckyLUKS.mo'.format(loc.split('_')[0]) ) except IOError: pass if l10n_resource is None: translation = NullTranslations() else: translation = GNUTranslations(io.BytesIO(l10n_resource)) main.luckyLUKS(translation) luckyLUKS-2.1.0/luckyLUKS/000077500000000000000000000000001420343070100151605ustar00rootroot00000000000000luckyLUKS-2.1.0/luckyLUKS/__init__.py000066400000000000000000000001411420343070100172650ustar00rootroot00000000000000""" luckyLUKS """ VERSION_STRING = '2.1.0' PROJECT_URL = 'https://github.com/jas-per/luckyLUKS' luckyLUKS-2.1.0/luckyLUKS/locale/000077500000000000000000000000001420343070100164175ustar00rootroot00000000000000luckyLUKS-2.1.0/luckyLUKS/locale/de/000077500000000000000000000000001420343070100170075ustar00rootroot00000000000000luckyLUKS-2.1.0/luckyLUKS/locale/de/LC_MESSAGES/000077500000000000000000000000001420343070100205745ustar00rootroot00000000000000luckyLUKS-2.1.0/luckyLUKS/locale/de/LC_MESSAGES/luckyLUKS.po000066400000000000000000000773531420343070100230010ustar00rootroot00000000000000# German translations for luckyLUKS. # Copyright (C) 2014 Jasper van Hoorn (muzius@gmail.com) # This file is distributed under the same license as the luckyLUKS project. # msgid "" msgstr "" "Project-Id-Version: luckyLUKS 0.9.1\n" "Report-Msgid-Bugs-To: Jasper van Hoorn (muzius@gmail.com)\n" "POT-Creation-Date: 2022-02-16 23:09+0100\n" "PO-Revision-Date: 2022-02-16 23:42+0100\n" "Last-Translator: Jasper van Hoorn \n" "Language: de\n" "Language-Team: de \n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" "X-Generator: Poedit 2.3\n" #: luckyLUKS/main.py:33 msgid "GUI for creating and unlocking LUKS/TrueCrypt volumes from container files" msgstr "" "GUI zum Erstellen und Öffnen von verschlüsselten Containern (LUKS/TrueCrypt)" #: luckyLUKS/main.py:34 msgid "" "When called without any arguments a setup dialog will be shown before " "unlocking,\n" "where you can select containerfile and name, or create a new encrypted " "container.\n" "If both arguments are supplied, the unlock dialog will be shown directly.\n" "\n" "Example:\n" " {executable} -c /usbstick/encrypted.bin -n mydata -m /home/user/enc\n" "\n" "If automatic mounting (eg udisks/polkit) is configured on your system,\n" "explicitly setting a mountpoint is usually not needed (but still possible)\n" "\n" "Homepage: {project_url}" msgstr "" "Wird das Programm ohne Parameter aufgerufen, erscheint zunächst ein " "Dialogfenster,\n" "um die Containerdatei und einen Namen auszuwahlen, oder einen neuen " "verschlüsselten\n" "Container zu erstellen.\n" "Werden beide Parameter übergeben, wird der Dialog zum Öffnen des Containers " "direkt angezeigt.\n" "\n" "Beispiel:\n" " {executable} -c /usbstick/geheim.bin -n MeineDateien -m /home/user/Dokumente\n" "\n" "Ist das Betriebssystem so konfiguriert, dass neue Datenträger automatisch " "eingebunden werden\n" "braucht ein Pfad zum Einhängen des Containers nicht unbedingt angegeben " "werden.\n" "\n" "Homepage: {project_url}" #. L10n: used by argsparse to generate help output on the console (luckyLUKS #. --help) #: luckyLUKS/main.py:47 msgid "usage: " msgstr "Aufruf: " #. L10n: used by argsparse to generate help output on the console (luckyLUKS #. --help) #: luckyLUKS/main.py:49 msgid "optional arguments" msgstr "Optionale Parameter" #. L10n: used by argsparse to generate help output on the console (luckyLUKS #. --help) #: luckyLUKS/main.py:51 msgid "show this help message and exit" msgstr "Diese Hilfe anzeigen und beenden" #. L10n: used by argsparse to generate help output on the console (luckyLUKS #. --help) #: luckyLUKS/main.py:53 #, python-format msgid "%(prog)s: error: %(message)s\n" msgstr "%(prog)s Fehler: %(message)s\n" #. L10n: used by argsparse to generate help output on the console (luckyLUKS #. --help) #: luckyLUKS/main.py:55 #, python-format msgid "unrecognized arguments: %s" msgstr "Unbekannter Parameter: %s" #: luckyLUKS/main.py:59 luckyLUKS/main.py:63 luckyLUKS/main.py:65 msgid "PATH" msgstr "PFAD" #: luckyLUKS/main.py:60 msgid "Path to the encrypted container file" msgstr "Pfad zur verschlüsselten Container Datei" #: luckyLUKS/main.py:61 msgid "NAME" msgstr "NAME" #: luckyLUKS/main.py:62 msgid "Choose a device name to identify the unlocked container" msgstr "Frei wählbarer Name zur Identifikation des geöffneten Containers" #: luckyLUKS/main.py:64 msgid "Where to mount the encrypted filesystem" msgstr "Pfad zum Einhängen des geöffneten Containers" #: luckyLUKS/main.py:66 msgid "Path to an optional key file" msgstr "Pfad zu einer optionalen Schlüsseldatei" #: luckyLUKS/main.py:68 msgid "show program's version number and exit" msgstr "Versionsinformation anzeigen und beenden" #. L10n: program name - translatable for startmenu titlebar etc #: luckyLUKS/mainUI.py:65 luckyLUKS/setupUI.py:48 luckyLUKS/setupUI.py:507 #: luckyLUKS/unlockUI.py:116 msgid "luckyLUKS" msgstr "luckyLUKS" #: luckyLUKS/mainUI.py:69 msgid "" "{program_name} executable not found!\n" "Please install, eg for Debian/Ubuntu\n" "`apt-get install {program_name}`" msgstr "" "{program_name} konnte nicht gefunden werden!\n" "Bitte installieren, z.B. unter Debian/Ubuntu\n" "`apt-get install {program_name}`" #: luckyLUKS/mainUI.py:78 msgid "" "Graphical programs should not be run as root!\n" "Please call as normal user." msgstr "" "Grafische Programme sollten nicht\n" "als Administrator gestarten werden!\n" "Bitte als normaler Nutzer ausführen." #: luckyLUKS/mainUI.py:84 luckyLUKS/worker.py:206 msgid "" "Container file not accessible\n" "or path does not exist:\n" "\n" "{file_path}" msgstr "" "Kein Zugriff auf die gewünschte Container Datei:\n" "\n" "{file_path}" #: luckyLUKS/mainUI.py:91 msgid "" "Invalid arguments:\n" "Please call without any arguments\n" "or supply both container and name.\n" "\n" "{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]\n" "\n" "CONTAINER = Path of the encrypted container file\n" "NAME = A (unique) name to identify the unlocked container\n" "Optional: MOUNTPOINT = where to mount the encrypted filesystem\n" "\n" "If automatic mounting is configured on your system,\n" "explicitly setting a mountpoint is not required\n" "\n" "For more information, visit\n" "{project_url}" msgstr "" "Parameter unvollständig:\n" "Bitte mit Container Datei und Name,\n" "oder ganz ohne Parameter aufrufen.\n" "\n" "{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]\n" "\n" "CONTAINER = Pfad zur verschlüsselten Container Datei\n" "NAME = Frei wählbarer Name zur Identifikation des Containers\n" "Optional: MOUNTPOINT = Pfad zum Einhängen des Containers\n" "\n" "Ist das Betriebssystem so konfiguriert, dass neue Datenträger\n" "automatisch eingebunden werden (z.B. mit udisks/polkit),\n" "braucht ein Pfad zum Einhängen des Containers\n" "nicht unbedingt angegeben werden.\n" "\n" "Weitere Informationen auf der Homepage\n" "{project_url}" #: luckyLUKS/mainUI.py:151 msgid "Handle encrypted container" msgstr "Verschlüsselten Container verwalten" #: luckyLUKS/mainUI.py:153 msgid "Name:" msgstr "Name:" #: luckyLUKS/mainUI.py:157 msgid "File:" msgstr "Datei:" #: luckyLUKS/mainUI.py:161 msgid "Key:" msgstr "Schlüssel:" #: luckyLUKS/mainUI.py:165 msgid "Mount:" msgstr "Pfad:" #: luckyLUKS/mainUI.py:168 msgid "Status:" msgstr "Status:" #: luckyLUKS/mainUI.py:190 luckyLUKS/mainUI.py:243 msgid "Hide" msgstr "Minimieren" #: luckyLUKS/mainUI.py:193 luckyLUKS/mainUI.py:266 luckyLUKS/setupUI.py:589 #: luckyLUKS/unlockUI.py:191 msgid "Quit" msgstr "Beenden" #: luckyLUKS/mainUI.py:211 msgid "Container is {unlocked_green_bold}" msgstr "Container ist {unlocked_green_bold}" #: luckyLUKS/mainUI.py:212 msgid "unlocked" msgstr "geöffnet" #: luckyLUKS/mainUI.py:213 msgid "Close Container" msgstr "Container Schliessen" #: luckyLUKS/mainUI.py:215 msgid "{device_name} is unlocked" msgstr "{device_name} ist geöffnet" #: luckyLUKS/mainUI.py:217 msgid "Container is {closed_red_bold}" msgstr "Container ist {closed_red_bold}" #: luckyLUKS/mainUI.py:218 msgid "closed" msgstr "verschlossen" #: luckyLUKS/mainUI.py:219 luckyLUKS/setupUI.py:121 msgid "Unlock Container" msgstr "Container Öffnen" #: luckyLUKS/mainUI.py:221 msgid "{device_name} is closed" msgstr "{device_name} ist verschlossen" #: luckyLUKS/mainUI.py:240 luckyLUKS/mainUI.py:252 msgid "Show" msgstr "Anzeigen" #: luckyLUKS/mainUI.py:261 msgid "" "{device_name} >> {container_path}\n" "is currently unlocked,\n" "Close Container now and quit?" msgstr "" "{device_name} >> {container_path}\n" "ist zur Zeit geöffnet,\n" "Container jetzt schliessen\n" "und das Programm beenden?" #: luckyLUKS/mainUI.py:296 msgid "Closing Container .." msgstr "Schliesse Container .." #: luckyLUKS/mainUI.py:327 luckyLUKS/unlockUI.py:213 msgid "Initializing .." msgstr "Initialisiere .." #: luckyLUKS/setupUI.py:67 luckyLUKS/setupUI.py:758 msgid "Unlock an encrypted container\n" msgstr "Verschlüsselten Container öffnen\n" #: luckyLUKS/setupUI.py:68 msgid "Please select container file and name" msgstr "Bitte Container Datei und Namen wählen" #: luckyLUKS/setupUI.py:72 luckyLUKS/setupUI.py:130 msgid "container file" msgstr "Container Datei" #: luckyLUKS/setupUI.py:78 msgid "choose file" msgstr "Datei auswählen" #: luckyLUKS/setupUI.py:82 luckyLUKS/setupUI.py:140 msgid "device name" msgstr "Gerätename" #: luckyLUKS/setupUI.py:88 luckyLUKS/setupUI.py:163 msgid "Advanced" msgstr "Erweitert" #: luckyLUKS/setupUI.py:91 luckyLUKS/setupUI.py:166 luckyLUKS/setupUI.py:725 #: luckyLUKS/setupUI.py:766 msgid "key file" msgstr "Schlüssel Datei" #: luckyLUKS/setupUI.py:97 luckyLUKS/setupUI.py:172 msgid "choose keyfile" msgstr "Schlüsseldatei auswählen" #: luckyLUKS/setupUI.py:102 luckyLUKS/setupUI.py:776 msgid "mount point" msgstr "Einhängepunkt" #: luckyLUKS/setupUI.py:108 msgid "choose folder" msgstr "Verzeichnis auswählen" #: luckyLUKS/setupUI.py:115 luckyLUKS/setupUI.py:208 luckyLUKS/utilsUI.py:48 msgid "Help" msgstr "Hilfe" #: luckyLUKS/setupUI.py:125 luckyLUKS/setupUI.py:709 msgid "Create a new encrypted container\n" msgstr "Neuen verschlüsselten Container erstellen\n" #: luckyLUKS/setupUI.py:126 msgid "Please choose container file, name and size" msgstr "Bitte Container Datei, Namen und Grösse wählen" #: luckyLUKS/setupUI.py:136 msgid "set file" msgstr "Datei auswählen" #: luckyLUKS/setupUI.py:146 msgid "container size" msgstr "Container Grösse" #: luckyLUKS/setupUI.py:159 msgid "Quickformat" msgstr "Quickformat" #: luckyLUKS/setupUI.py:177 msgid "Create key file" msgstr "Schlüsseldatei erzeugen" #: luckyLUKS/setupUI.py:182 msgid "format" msgstr "Format" #: luckyLUKS/setupUI.py:194 luckyLUKS/setupUI.py:747 msgid "filesystem" msgstr "Dateisystem" #: luckyLUKS/setupUI.py:214 msgid "Create New Container" msgstr "Neuen Container erstellen" #: luckyLUKS/setupUI.py:219 luckyLUKS/setupUI.py:595 luckyLUKS/setupUI.py:610 #: luckyLUKS/setupUI.py:617 luckyLUKS/unlockUI.py:227 msgid "Unlock" msgstr "Öffnen" #: luckyLUKS/setupUI.py:242 msgid "Creating new container\n" msgstr "Erstelle neuen Container\n" #: luckyLUKS/setupUI.py:243 msgid "patience .. this might take a while" msgstr "bitte Geduld .. das kann eine Weile dauern" #: luckyLUKS/setupUI.py:247 luckyLUKS/setupUI.py:289 luckyLUKS/setupUI.py:316 msgid "Step" msgstr "Schritt" #: luckyLUKS/setupUI.py:248 msgid "Initializing Container File" msgstr "Erstelle Container Datei" #: luckyLUKS/setupUI.py:290 msgid "Initializing Encryption" msgstr "Initialisiere Verschlüsselung" #: luckyLUKS/setupUI.py:304 msgid "Initialize container aborted" msgstr "Abbruch durch den Benutzer" #: luckyLUKS/setupUI.py:317 msgid "Initializing Filesystem" msgstr "Erstelle Dateisystem" #: luckyLUKS/setupUI.py:333 msgid "" "{device_name}\n" "successfully created!\n" "Click on unlock to use the new container" msgstr "" "{device_name}\n" "erfolgreich erstellt!\n" "Auf `Öffnen` klicken, um den neuen Container zu benutzen" #: luckyLUKS/setupUI.py:334 luckyLUKS/setupUI.py:471 luckyLUKS/setupUI.py:551 #: luckyLUKS/utils.py:70 luckyLUKS/utils.py:141 msgid "Success" msgstr "Erfolgreich" #: luckyLUKS/setupUI.py:377 msgid "Done" msgstr "Abgeschlossen" #: luckyLUKS/setupUI.py:427 msgid "new_keyfile.bin" msgstr "neuer_schlüssel.bin" #: luckyLUKS/setupUI.py:436 msgid "Creating key file" msgstr "Schlüsseldatei wird erstellt" #: luckyLUKS/setupUI.py:443 msgid "" "This might take a while. Since computers are deterministic machines\n" "it is quite a challenge to generate real random data for the key.\n" "\n" "You can speed up the process by typing, moving the mouse\n" "and generally use the computer while the key gets generated." msgstr "" "Bitte Geduld, das kann eine Weile dauern. Das Erzeugen von\n" "Zufallsdaten ist eine echte Herausforderung für Computer.\n" "\n" "Der Vorgang kann beschleunigt werden, durch nicht vorher-\n" "sehbare Aktionen, wie Mausbewegungen, Tastatureingaben \n" "und allgemein durch die aktive Benutzung des Computers\n" "während der Erzeugung des Schlüssels." #: luckyLUKS/setupUI.py:469 msgid "" "{key_file}\n" "successfully created!\n" "You can use this key file now,\n" "to create a new container." msgstr "" "{key_file}\n" "erfolgreich erstellt!\n" "Diese Schlüsseldatei kann jetzt mit\n" "einem neuen Container benutzt werden." #: luckyLUKS/setupUI.py:477 msgid "" "Successfully unlocked!\n" "\n" "Do you want to create\n" "a startup menu entry for {device_name}?\n" "\n" "-> Your password will NOT be saved!\n" " This just creates a shortcut,\n" " to the unlock container dialog.\n" msgstr "" "Container erfolgreich geöffnet!\n" "\n" "Soll ein Eintrag ins Startmenü für\n" "{device_name} erstellt werden?\n" "\n" "-> Das Passwort wird NICHT gespeichert!\n" " Es wird nur eine Abkürzung zum\n" " `Container öffnen` Dialog erstellt.\n" #: luckyLUKS/setupUI.py:486 msgid "Create shortcut" msgstr "Eintrag erstellen" #: luckyLUKS/setupUI.py:487 msgid "No, thanks" msgstr "Nein, danke" #: luckyLUKS/setupUI.py:520 msgid "Unlock {device_name}" msgstr "{device_name} öffnen" #: luckyLUKS/setupUI.py:523 msgid "Encrypted Container Tool" msgstr "Programm für verschlüsselte Container" #: luckyLUKS/setupUI.py:524 msgid "Encrypted Container" msgstr "Verschlüsselter Container" #: luckyLUKS/setupUI.py:551 msgid "" "` {name} `\n" "added to start menu" msgstr "" "` {name} `\n" "Zum Startmenu hinzugefügt" #: luckyLUKS/setupUI.py:559 luckyLUKS/setupUI.py:566 msgid "" "Adding to start menu not possible,\n" "please place your shortcut manually.\n" "\n" "Desktop file saved to\n" "{location}" msgstr "" "Automatisches Hinzufügen zum Startmenu\n" "nicht möglich, bitte manuell ausführen.\n" "\n" "Desktop-Datei wurde gespeichert unter:\n" "{location}" #: luckyLUKS/setupUI.py:587 msgid "" "Currently processing your request!\n" "Do you really want to quit?" msgstr "" "Der Auftrag wird noch bearbeitet!\n" "Das Programm wirklich beenden?" #: luckyLUKS/setupUI.py:598 msgid "" "No tools to format the filesystem found\n" "Please install, eg for Debian/Ubuntu\n" "`apt-get install e2fslibs ntfs-3g`" msgstr "" "Es konnten keine Programme zum Erstellen des Dateisystems gefunden werden!\n" "Bitte installieren, z.B. unter Debian/Ubuntu `apt-get install e2fslibs ntfs-3g`" #: luckyLUKS/setupUI.py:601 luckyLUKS/setupUI.py:655 msgid "Create" msgstr "Erstellen" #: luckyLUKS/setupUI.py:606 msgid "Please choose a container file" msgstr "Bitte Container Datei auswählen" #: luckyLUKS/setupUI.py:615 msgid "Please choose a folder as mountpoint" msgstr "Bitte ein Verzeichnis als Einhängepunkt auswählen" #: luckyLUKS/setupUI.py:621 msgid "Please choose a key file" msgstr "Bitte Schlüsseldatei auswählen" #: luckyLUKS/setupUI.py:634 msgid "new_container.bin" msgstr "neuer_container.bin" #: luckyLUKS/setupUI.py:650 msgid "Please create a new file" msgstr "Bitte neue Datei erstellen" #: luckyLUKS/setupUI.py:658 msgid "" "File already exists:\n" "{filename}\n" "\n" "Please create a new file!" msgstr "" "Datei existiert bereits:\n" "{filename}\n" "\n" "Bitte als neue Datei erstellen!" #: luckyLUKS/setupUI.py:710 msgid "" "Enter the path of the new container file in the textbox or click the " "button next to the box for a graphical create file dialog.\n" "\n" "The device name will be used to identify the unlocked container. It can " "be any name up to 16 unicode characters, as long as it is unique.\n" "\n" "The size of the container can be provided in GB or MB. The container " "will get initialized with random data, this can take quite a while - 1 hour for " "a 10GB container on an external drive is nothing unusual.\n" "\n" "To speed up container creation Quickformat can be enabled to use " "`fallocate` instead of initializing the container with random data - this means " "previous data will not be overwritten and some conclusions about encrypted data " "inside closed containers can be drawn.\n" msgstr "" "Eingabe des Names der neuen Container Datei über das Textfeld, oder über " "einen graphischen Dialog durch Drücken des Knopfes daneben.\n" "\n" "Anhand des Gerätenamens wird der geöffnete Container identifiziert. " "Jeder Name mit bis zu 16 Buchstaben Länge ist möglich, solange nicht zwei " "geöffnete Container denselben Namen haben.\n" "\n" "Die Grösse der Container Datei kann in GB oder MB angegeben werden. Es " "kann eine Weile dauern, den Container mit Zufallsdaten zu initialisieren. " "( 1Stunde pro 10GB sind bei externen Laufwerken nicht ungewöhnlich).\n" "\n" "Um diesen Prozess zu beschleunigen kann Quickformat ausgewählt und " "`fallocate` zum Initialisieren benutzt werden - damit werden allerdings alte " "Datenreste möglicherweise nicht überschrieben und einige (meist unkritische) " "Informationen über die verschlüsselten Daten im Container von aussen " "ersichtlich. \n" #: luckyLUKS/setupUI.py:726 luckyLUKS/setupUI.py:767 msgid "" "A key file can be used to allow access to an encrypted container instead of a " "password. Using a key file resembles unlocking a door with a key in the real " "world - anyone with access to the key file can open your encrypted container. " "Make sure to store it at a protected location. Its okay to store it on your " "computer if you are using an already encrypted harddrive or a digital keystore. " "Having the key file on a small USB drive attached to your real chain of keys would be an " "option as well.\n" "Since you dont have to enter a password, using a key file can be a convenient " "way to access your encrypted container. Just make sure you dont lose the key " "(file) ;)" msgstr "" "Anstelle eines Passwortes kann auch eine Schlüsseldatei zum Öffnen eines " "verschlüsselten Containers benutzt werden. Eine Schlüsseldatei ähnelt " "prinzipiell einem physischen Schlüssel, da ein jeder der in den Besitz dieser " "Schlüsseldatei kommt, auch den damit verschlossenen Container öffnen kann. Die " "Schlüsseldatei sollte daher an einem sicheren Ort gespeichert werden. Dies kann " "auch der eigene Computer sein, wenn dessen Festplatte ohnehin mit dem " "Nutzerpasswort verschlüsselt wurde. Alternativ bieten sich zum Beispiel USB-Sticks an, die " "an ein echtes Schlüsselbund angehängt werden können.\n" "Da kein Passwort eingegeben werden muss, bieten Schlüsseldateien eine bequeme " "Art zum Öffnen von verschlüsselten Containern. Beachtet werden muss allerdings, " "dass die Schlüsseldatei nicht verloren geht, oder in falsche Hände gerät." #: luckyLUKS/setupUI.py:735 msgid "" "\n" "\n" "Although basically any file could be used as a key file, a file with " "predictable content leads to similar problems as using weak passwords. Audio " "files or pictures are a good choice. If unsure use the `create key file` button " "to generate a small key file filled with random data." msgstr "" "\n" "\n" "Auch wenn grundsätzlich jede Datei als Schlüssel benutzt werden könnte, führen " "vorhersehbare Daten zu ähnlichen Problemen, wie einfache Passwörter. Gut " "geeignet sind Audiodateien oder Bilder, alternativ kann mit der `Schlüsseldatei " "erzeugen` Funktion auch eine kleine Datei erstellt werden, die sich optimal als " "Schlüssel eignet." #: luckyLUKS/setupUI.py:740 msgid "encryption format" msgstr "Verschlüsselungsformat" #: luckyLUKS/setupUI.py:741 msgid "" "The standard disk encryption format on Linux is called LUKS. With LibreCrypt you can use LUKS " "containers on Windows as well. The TrueCrypt format is quite popular on Windows/" "Mac, and can be created on Linux if `tcplay` is installed. Please note, that " "\"hidden\" TrueCrypt partitions are not supported by luckyLUKS!" msgstr "" "Das Standard Verschlüsselungs-Format unter Linux ist LUKS. Mit LibreCrypt können LUKS Container " "auch unter Windows genutzt werden. Populärer unter Windows ist das TrueCrypt " "Format. TrueCrypt Container können auch mit Linux erstellt werden, wenn " "`tcplay` installiert ist. Sogenannte \"hidden\" TrueCrypt Container werden " "allerdings nicht von luckyLUKS unterstützt!" #: luckyLUKS/setupUI.py:748 msgid "" "Choose the ntfs filesystem to be able to access your data from Linux, Windows " "and Mac OSX. Since access permissions cannot be mapped from ntfs to Linux, " "access to ntfs devices is usually not restricted -> take care when using " "unlocked ntfs devices in a multiuser environment!" msgstr "" "Auf das NTFS Dateisystem kann sowohl unter Windows als auch unter Linux " "zugegriffen werden, allerdings ist der Zugriff auf eingehängte NTFS Geräte " "unter Linux oft nicht auf den aktuellen Benutzer begrenzt. Vorsicht also beim " "Öffnen von NTFS-Containern auf Rechnern mit mehreren Benutzern, möglichst den " "Container vor jeden Benutzerwechsel schliessen!" #: luckyLUKS/setupUI.py:759 msgid "" "Select the encrypted container file by clicking the button next to the " "textbox. Both LUKS and Truecrypt containers are supported!\n" "\n" "The device name will be used to identify the unlocked container. It can " "be any name up to 16 unicode characters, as long as it is unique -> you cannot " "give two unlocked containers the same name" msgstr "" "Die Auswahl der verschlüsselten Container Datei ist per Dialog durch " "Drücken des Knopfes neben dem Textfeld möglich. Sowohl LUKS als auch Truecrypt " "Container werden unterstützt!\n" "\n" "Anhand des Gerätenamens wird der geöffnete Container identifiziert. " "Jeder Name mit bis zu 16 Buchstaben Länge ist möglich, solange nicht zwei " "geöffnete Container denselben Namen haben." #: luckyLUKS/setupUI.py:777 msgid "" "The mount point is the folder on your computer, where you can access the files " "inside the container after unlocking. If automatic mounting is configured on " "your system (eg with udisks), explicitly setting a mountpoint is not neccessary " "(but still possible)." msgstr "" "Der Einhängepunkt ist das Verzeichnis auf dem Rechner, in dem die Inhalte nach " "dem Öffnen des Containers erscheinen. Wenn das verwendete System externe Medien " "automatisch einhängt, braucht ein Einhängepunkt nicht explizit angegeben werden." #: luckyLUKS/unlockUI.py:122 msgid "" "Always allow luckyLUKS to be run\n" "with administrative privileges" msgstr "" "Administrator Rechte für luckyLUKS \n" "dauerhaft freigeben" #: luckyLUKS/unlockUI.py:153 msgid "" "Please choose a passphrase\n" "to encrypt the new container:\n" msgstr "" "Bitte ein Passwort zum Verschlüsseln\n" "des neuen Containers wählen:\n" #: luckyLUKS/unlockUI.py:154 msgid "Enter new Passphrase" msgstr "Neues Passwort eingeben" #: luckyLUKS/unlockUI.py:156 msgid "Display passphrase" msgstr "Passwort anzeigen" #: luckyLUKS/unlockUI.py:189 msgid "" "Currently creating new container!\n" "Do you really want to quit?" msgstr "" "Es wird zur Zeit ein neuer Container erstellt!\n" "Das Programm wirklich beenden?" #: luckyLUKS/unlockUI.py:220 msgid "" "Using keyfile\n" "{keyfile}\n" "to open container.\n" "\n" "Please wait .." msgstr "" "Benutze Schlüsseldatei\n" "{keyfile}\n" "um den Container zu öffnen.\n" "\n" "Bitte einen Moment Geduld .." #: luckyLUKS/unlockUI.py:254 msgid "Checking passphrase .." msgstr "Überprüfe Passwort .." #: luckyLUKS/unlockUI.py:280 msgid "" "Please enter\n" "container passphrase:" msgstr "" "Bitte das Passwort zum Öffnen\n" "des verschlüsselten Containers eingeben:" #: luckyLUKS/unlockUI.py:282 msgid "" "Wrong passphrase, please retry!\n" "Enter container passphrase:" msgstr "" "Falsches Passwort, bitte erneut versuchen!\n" "Passwort des verschlüsselten Containers eingeben:" #: luckyLUKS/utils.py:64 msgid "" "Permanent `sudo` authorization for\n" "{program}\n" "has been successfully added for user `{username}` to \n" "/etc/sudoers.d/lucky-luks\n" msgstr "" "Administrator Rechte für\n" "{program}\n" "erfolgreich in /etc/sudoers.d/lucky-luks\n" "für den Benutzer `{username}` freigegeben.\n" #: luckyLUKS/utils.py:79 msgid "" "luckyLUKS needs administrative privileges.\n" "Please enter your password:" msgstr "" "luckyLUKS benötigt Administrator Rechte.\n" "Bitte Passwort eingeben:" #: luckyLUKS/utils.py:98 luckyLUKS/utils.py:126 msgid "Sorry, incorrect password.\n" msgstr "Falsches Passwort, bitte erneut versuchen.\n" #: luckyLUKS/utils.py:114 msgid "" "You are not allowed to execute this script with `sudo`.\n" "If you want to modify your `sudo` configuration,\n" "please enter the root/administrator password.\n" msgstr "" "Fehlende Berechtigung, dieses Programm mit `sudo` auszuführen.\n" "Um die Konfiguration von `sudo` anzupassen,\n" "bitte das root/Administrator Passwort eingeben.\n" #: luckyLUKS/utils.py:137 msgid "" "`sudo` configuration successfully modified, now\n" "you can use luckyLUKS with your user password.\n" "\n" "If you want to grant permanent administrative rights\n" "just tick the checkbox in the following dialog.\n" msgstr "" "Konfiguration von `sudo` erfolgreich geändert, Starten von\n" "luckyLUKS ist jetzt mit dem Passwort des Nutzers möglich.\n" "\n" "Um Administratorrechte dauerhaft zu vergeben\n" "einfach die Checkbox im folgenden Dialog auswählen.\n" #: luckyLUKS/utils.py:162 msgid "" "Communication with sudo process failed\n" "{error}" msgstr "" "Fehler in der Kommunikation mit `sudo`\n" "{error}" #: luckyLUKS/utils.py:224 luckyLUKS/utils.py:232 luckyLUKS/utils.py:259 #: luckyLUKS/worker.py:130 msgid "" "Error in communication:\n" "{error}" msgstr "" "Kommunikationsfehler:\n" "{error}" #: luckyLUKS/utils.py:315 msgid "" "Error while creating key file:\n" "{error}" msgstr "" "Fehler beim Erstellen der Schlüsseldatei:\n" "{error}" #: luckyLUKS/utilsUI.py:69 msgid "Advanced Topics:" msgstr "Erweiterte Optionen:" #: luckyLUKS/utilsUI.py:93 msgid "" "luckyLUKS version {version}\n" "For more information, visit\n" "{project_url}" msgstr "" "luckyLUKS Version {version}\n" "Weitere Informationen auf der Homepage\n" "{project_url}" #: luckyLUKS/utilsUI.py:135 msgid "Error" msgstr "Fehler" #: luckyLUKS/worker.py:72 msgid "Please call with sudo." msgstr "Bitte mit sudo aufrufen." #: luckyLUKS/worker.py:75 msgid "" "Missing information of the calling user in sudo environment.\n" "Please make sure sudo is configured correctly." msgstr "" "Sudo Informationen unvollständig - Aufrufender Benutzer nicht angegeben\n" "Bitte sicherstellen, dass Sudo richtig konfiguriert ist." #: luckyLUKS/worker.py:121 msgid "Helper process received unknown command" msgstr "Hilfsprozess hat eine unbekannte Anweisung bekommen" #: luckyLUKS/worker.py:201 luckyLUKS/worker.py:454 msgid "Device Name is empty" msgstr "Kein Gerätename angegeben" #: luckyLUKS/worker.py:215 msgid "" "Could not use container:\n" "{file_path}\n" "{device_name} is already unlocked\n" "using a different container\n" "Please change the name to unlock this container" msgstr "" "Container konnte nicht verwendet werden:\n" "{file_path}\n" "Der Name {device_name} wird bereits\n" "von einem anderen Container verwendet.\n" "Bitte den Namen ändern, um diesen Container zu öffnen" #: luckyLUKS/worker.py:231 msgid "" "Device Name too long:\n" "Only up to 16 characters possible, even less for unicode\n" "(roughly 8 non-ascii characters possible)" msgstr "" "Gerätename zu lang:\n" "Nur bis zu 16 Buchstaben möglich,\n" "wird Unicode verwendet etwas weniger.\n" "(ungefähr 8 nicht-ASCII Buchstaben möglich)" #: luckyLUKS/worker.py:238 luckyLUKS/worker.py:458 msgid "" "Illegal Device Name!\n" "Names starting with `-` or using `/` are not possible" msgstr "" "Gerätename nicht erlaubt!\n" "Namen, die mit einem Bindestrich anfangen (`-`),\n" "oder `/` verwenden sind nicht möglich." #: luckyLUKS/worker.py:258 msgid "" "Cannot use the container\n" "{file_path}\n" "The container is already in use ({existing_device})." msgstr "" "Container kann nicht benutzt werden\n" "{file_path}\n" "Der Container wird bereits verwendet ({existing_device})." #: luckyLUKS/worker.py:270 luckyLUKS/worker.py:497 msgid "" "Key file not accessible\n" "or path does not exist:\n" "\n" "{file_path}" msgstr "" "Kein Zugriff auf die gewünschte Schlüssel Datei:\n" "\n" "{file_path}" #: luckyLUKS/worker.py:279 msgid "" "Mount point not accessible\n" "or path does not exist:\n" "\n" "{mount_dir}" msgstr "" "Kein Zugriff auf den gewünschten Einhängepunkt möglich:\n" "\n" "{mount_dir}" #: luckyLUKS/worker.py:284 msgid "" "Already mounted at mount point:\n" "\n" "{mount_dir}" msgstr "" "Der gewünschte Einhängepunkt wird bereits verwendet:\n" "\n" "{mount_dir}" #: luckyLUKS/worker.py:289 msgid "" "Designated mount directory\n" "{mount_dir}\n" "is not empty" msgstr "" "Der gewünschte Einhängepunkt\n" "{mount_dir}\n" "ist nicht leer." #: luckyLUKS/worker.py:368 msgid "" "Open container failed.\n" "Please check key file" msgstr "" "Öffnen des Containers fehlgeschlagen.\n" "Bitte Schlüsseldatei überprüfen." #: luckyLUKS/worker.py:404 msgid "Unable to close container, device is busy" msgstr "Container wird zur Zeit verwendet, Schliessen nicht möglich." #: luckyLUKS/worker.py:450 msgid "Container Filename not supplied" msgstr "Keine Container Datei angegeben" #: luckyLUKS/worker.py:462 msgid "" "Device Name too long:\n" "Only up to 16 characters possible, even less for unicode \n" "(roughly 8 non-ascii characters possible)" msgstr "" "Gerätename zu lang:\n" "Nur bis zu 16 Buchstaben möglich,\n" "wird Unicode verwendet etwas weniger.\n" "(ungefähr 8 nicht-ASCII Buchstaben möglich)" #: luckyLUKS/worker.py:467 msgid "" "Device Name already in use:\n" "\n" "{device_name}" msgstr "" "Gerätename wird bereits verwendet:\n" "\n" "{device_name}" #: luckyLUKS/worker.py:471 msgid "" "Container size too small\n" "to create encrypted filesystem\n" "Please choose at least 5MB" msgstr "" "Grösse der Container Datei zu klein,\n" "um ein verschlüsseltes Dateisystem anzulegen.\n" "Bitte mindestens 5MB auswählen." #: luckyLUKS/worker.py:485 msgid "" "Not enough free disc space for container:\n" "\n" "{space_needed} MB needed\n" "{space_available} MB available" msgstr "" "Nicht genügend freier Speciherplatz für den Container:\n" "\n" "{space_needed} MB benötigt\n" "{space_available} MB vorhanden" #: luckyLUKS/worker.py:504 msgid "Unknown filesystem type: {filesystem_type}" msgstr "Unbekanntes Dateisystem: {filesystem_type}" #: luckyLUKS/worker.py:507 msgid "" "If you want to use TrueCrypt containers\n" "make sure `cryptsetup` is at least version 1.6 (`cryptsetup --version`)\n" "and `tcplay` is installed (eg for Debian/Ubuntu `apt-get install tcplay`)" msgstr "" "Um einen TrueCrypt Container zu öffnen\n" "muss `cryptsetup` mindestens Version 1.6 sein (`cryptsetup --version`),\n" "und `tcplay` installiert sein (z.B. in Debian/Ubuntu `apt-get install tcplay`)" #: luckyLUKS/worker.py:598 msgid "Unknown encryption format: {enc_fmt}" msgstr "Unbekanntes Verschlüsselungsformat: {enc_fmt}" #: luckyLUKS/worker.py:653 msgid "Cannot change sudo rights, invalid username" msgstr "Ungültiger Benutzername, Ändern der sudo-Rechte nicht möglich." #: luckyLUKS/worker.py:659 msgid "" "I`m afraid I can`t do that.\n" "\n" "To be able to permit permanent changes to sudo rights,\n" "please make sure the program is owned by root\n" "and not writeable by others.\n" "Execute the following commands in your shell:\n" "\n" "chmod 755 {program}\n" "sudo chown root:root {program}\n" "\n" msgstr "" "`Das kann ich nicht tun, Dave`\n" "\n" "Um dauerhafte Rechte mit `sudo` vergeben zu können,\n" "muss das Programm dem User `root` gehören,\n" "andere Nutzer dürfen es nicht verändern können!\n" "Bitte die folgenden Befehle auf der Kommandozeile eingeben:\n" "\n" "chmod 755 {program}\n" "sudo chown root:root {program}\n" "\n" luckyLUKS-2.1.0/luckyLUKS/locale/luckyLUKS.pot000066400000000000000000000463711420343070100210040ustar00rootroot00000000000000# Translations template for luckyLUKS. # Copyright (C) 2022 Jasper van Hoorn (muzius@gmail.com) # This file is distributed under the same license as the luckyLUKS project. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: luckyLUKS 2.1.0\n" "Report-Msgid-Bugs-To: Jasper van Hoorn (muzius@gmail.com)\n" "POT-Creation-Date: 2022-02-16 23:09+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" #: luckyLUKS/main.py:33 msgid "GUI for creating and unlocking LUKS/TrueCrypt volumes from container files" msgstr "" #: luckyLUKS/main.py:34 msgid "" "When called without any arguments a setup dialog will be shown before " "unlocking,\n" "where you can select containerfile and name, or create a new encrypted " "container.\n" "If both arguments are supplied, the unlock dialog will be shown directly." "\n" "\n" "Example:\n" " {executable} -c /usbstick/encrypted.bin -n mydata -m /home/user/enc\n" "\n" "If automatic mounting (eg udisks/polkit) is configured on your system,\n" "explicitly setting a mountpoint is usually not needed (but still " "possible)\n" "\n" "Homepage: {project_url}" msgstr "" #. L10n: used by argsparse to generate help output on the console (luckyLUKS #. --help) #: luckyLUKS/main.py:47 msgid "usage: " msgstr "" #. L10n: used by argsparse to generate help output on the console (luckyLUKS #. --help) #: luckyLUKS/main.py:49 msgid "optional arguments" msgstr "" #. L10n: used by argsparse to generate help output on the console (luckyLUKS #. --help) #: luckyLUKS/main.py:51 msgid "show this help message and exit" msgstr "" #. L10n: used by argsparse to generate help output on the console (luckyLUKS #. --help) #: luckyLUKS/main.py:53 #, python-format msgid "%(prog)s: error: %(message)s\n" msgstr "" #. L10n: used by argsparse to generate help output on the console (luckyLUKS #. --help) #: luckyLUKS/main.py:55 #, python-format msgid "unrecognized arguments: %s" msgstr "" #: luckyLUKS/main.py:59 luckyLUKS/main.py:63 luckyLUKS/main.py:65 msgid "PATH" msgstr "" #: luckyLUKS/main.py:60 msgid "Path to the encrypted container file" msgstr "" #: luckyLUKS/main.py:61 msgid "NAME" msgstr "" #: luckyLUKS/main.py:62 msgid "Choose a device name to identify the unlocked container" msgstr "" #: luckyLUKS/main.py:64 msgid "Where to mount the encrypted filesystem" msgstr "" #: luckyLUKS/main.py:66 msgid "Path to an optional key file" msgstr "" #: luckyLUKS/main.py:68 msgid "show program's version number and exit" msgstr "" #. L10n: program name - translatable for startmenu titlebar etc #: luckyLUKS/mainUI.py:65 luckyLUKS/setupUI.py:48 luckyLUKS/setupUI.py:507 #: luckyLUKS/unlockUI.py:116 msgid "luckyLUKS" msgstr "" #: luckyLUKS/mainUI.py:69 msgid "" "{program_name} executable not found!\n" "Please install, eg for Debian/Ubuntu\n" "`apt-get install {program_name}`" msgstr "" #: luckyLUKS/mainUI.py:78 msgid "" "Graphical programs should not be run as root!\n" "Please call as normal user." msgstr "" #: luckyLUKS/mainUI.py:84 luckyLUKS/worker.py:206 msgid "" "Container file not accessible\n" "or path does not exist:\n" "\n" "{file_path}" msgstr "" #: luckyLUKS/mainUI.py:91 msgid "" "Invalid arguments:\n" "Please call without any arguments\n" "or supply both container and name.\n" "\n" "{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]\n" "\n" "CONTAINER = Path of the encrypted container file\n" "NAME = A (unique) name to identify the unlocked container\n" "Optional: MOUNTPOINT = where to mount the encrypted filesystem\n" "\n" "If automatic mounting is configured on your system,\n" "explicitly setting a mountpoint is not required\n" "\n" "For more information, visit\n" "{project_url}" msgstr "" #: luckyLUKS/mainUI.py:151 msgid "Handle encrypted container" msgstr "" #: luckyLUKS/mainUI.py:153 msgid "Name:" msgstr "" #: luckyLUKS/mainUI.py:157 msgid "File:" msgstr "" #: luckyLUKS/mainUI.py:161 msgid "Key:" msgstr "" #: luckyLUKS/mainUI.py:165 msgid "Mount:" msgstr "" #: luckyLUKS/mainUI.py:168 msgid "Status:" msgstr "" #: luckyLUKS/mainUI.py:190 luckyLUKS/mainUI.py:243 msgid "Hide" msgstr "" #: luckyLUKS/mainUI.py:193 luckyLUKS/mainUI.py:266 luckyLUKS/setupUI.py:589 #: luckyLUKS/unlockUI.py:191 msgid "Quit" msgstr "" #: luckyLUKS/mainUI.py:211 msgid "Container is {unlocked_green_bold}" msgstr "" #: luckyLUKS/mainUI.py:212 msgid "unlocked" msgstr "" #: luckyLUKS/mainUI.py:213 msgid "Close Container" msgstr "" #: luckyLUKS/mainUI.py:215 msgid "{device_name} is unlocked" msgstr "" #: luckyLUKS/mainUI.py:217 msgid "Container is {closed_red_bold}" msgstr "" #: luckyLUKS/mainUI.py:218 msgid "closed" msgstr "" #: luckyLUKS/mainUI.py:219 luckyLUKS/setupUI.py:121 msgid "Unlock Container" msgstr "" #: luckyLUKS/mainUI.py:221 msgid "{device_name} is closed" msgstr "" #: luckyLUKS/mainUI.py:240 luckyLUKS/mainUI.py:252 msgid "Show" msgstr "" #: luckyLUKS/mainUI.py:261 msgid "" "{device_name} >> {container_path}\n" "is currently unlocked,\n" "Close Container now and quit?" msgstr "" #: luckyLUKS/mainUI.py:296 msgid "Closing Container .." msgstr "" #: luckyLUKS/mainUI.py:327 luckyLUKS/unlockUI.py:213 msgid "Initializing .." msgstr "" #: luckyLUKS/setupUI.py:67 luckyLUKS/setupUI.py:758 msgid "Unlock an encrypted container\n" msgstr "" #: luckyLUKS/setupUI.py:68 msgid "Please select container file and name" msgstr "" #: luckyLUKS/setupUI.py:72 luckyLUKS/setupUI.py:130 msgid "container file" msgstr "" #: luckyLUKS/setupUI.py:78 msgid "choose file" msgstr "" #: luckyLUKS/setupUI.py:82 luckyLUKS/setupUI.py:140 msgid "device name" msgstr "" #: luckyLUKS/setupUI.py:88 luckyLUKS/setupUI.py:163 msgid "Advanced" msgstr "" #: luckyLUKS/setupUI.py:91 luckyLUKS/setupUI.py:166 luckyLUKS/setupUI.py:725 #: luckyLUKS/setupUI.py:766 msgid "key file" msgstr "" #: luckyLUKS/setupUI.py:97 luckyLUKS/setupUI.py:172 msgid "choose keyfile" msgstr "" #: luckyLUKS/setupUI.py:102 luckyLUKS/setupUI.py:776 msgid "mount point" msgstr "" #: luckyLUKS/setupUI.py:108 msgid "choose folder" msgstr "" #: luckyLUKS/setupUI.py:115 luckyLUKS/setupUI.py:208 luckyLUKS/utilsUI.py:48 msgid "Help" msgstr "" #: luckyLUKS/setupUI.py:125 luckyLUKS/setupUI.py:709 msgid "Create a new encrypted container\n" msgstr "" #: luckyLUKS/setupUI.py:126 msgid "Please choose container file, name and size" msgstr "" #: luckyLUKS/setupUI.py:136 msgid "set file" msgstr "" #: luckyLUKS/setupUI.py:146 msgid "container size" msgstr "" #: luckyLUKS/setupUI.py:159 msgid "Quickformat" msgstr "" #: luckyLUKS/setupUI.py:177 msgid "Create key file" msgstr "" #: luckyLUKS/setupUI.py:182 msgid "format" msgstr "" #: luckyLUKS/setupUI.py:194 luckyLUKS/setupUI.py:747 msgid "filesystem" msgstr "" #: luckyLUKS/setupUI.py:214 msgid "Create New Container" msgstr "" #: luckyLUKS/setupUI.py:219 luckyLUKS/setupUI.py:595 luckyLUKS/setupUI.py:610 #: luckyLUKS/setupUI.py:617 luckyLUKS/unlockUI.py:227 msgid "Unlock" msgstr "" #: luckyLUKS/setupUI.py:242 msgid "Creating new container\n" msgstr "" #: luckyLUKS/setupUI.py:243 msgid "patience .. this might take a while" msgstr "" #: luckyLUKS/setupUI.py:247 luckyLUKS/setupUI.py:289 luckyLUKS/setupUI.py:316 msgid "Step" msgstr "" #: luckyLUKS/setupUI.py:248 msgid "Initializing Container File" msgstr "" #: luckyLUKS/setupUI.py:290 msgid "Initializing Encryption" msgstr "" #: luckyLUKS/setupUI.py:304 msgid "Initialize container aborted" msgstr "" #: luckyLUKS/setupUI.py:317 msgid "Initializing Filesystem" msgstr "" #: luckyLUKS/setupUI.py:333 msgid "" "{device_name}\n" "successfully created!\n" "Click on unlock to use the new container" msgstr "" #: luckyLUKS/setupUI.py:334 luckyLUKS/setupUI.py:471 luckyLUKS/setupUI.py:551 #: luckyLUKS/utils.py:70 luckyLUKS/utils.py:141 msgid "Success" msgstr "" #: luckyLUKS/setupUI.py:377 msgid "Done" msgstr "" #: luckyLUKS/setupUI.py:427 msgid "new_keyfile.bin" msgstr "" #: luckyLUKS/setupUI.py:436 msgid "Creating key file" msgstr "" #: luckyLUKS/setupUI.py:443 msgid "" "This might take a while. Since computers are deterministic machines\n" "it is quite a challenge to generate real random data for the key.\n" "\n" "You can speed up the process by typing, moving the mouse\n" "and generally use the computer while the key gets generated." msgstr "" #: luckyLUKS/setupUI.py:469 msgid "" "{key_file}\n" "successfully created!\n" "You can use this key file now,\n" "to create a new container." msgstr "" #: luckyLUKS/setupUI.py:477 msgid "" "Successfully unlocked!\n" "\n" "Do you want to create\n" "a startup menu entry for {device_name}?\n" "\n" "-> Your password will NOT be saved!\n" " This just creates a shortcut,\n" " to the unlock container dialog.\n" msgstr "" #: luckyLUKS/setupUI.py:486 msgid "Create shortcut" msgstr "" #: luckyLUKS/setupUI.py:487 msgid "No, thanks" msgstr "" #: luckyLUKS/setupUI.py:520 msgid "Unlock {device_name}" msgstr "" #: luckyLUKS/setupUI.py:523 msgid "Encrypted Container Tool" msgstr "" #: luckyLUKS/setupUI.py:524 msgid "Encrypted Container" msgstr "" #: luckyLUKS/setupUI.py:551 msgid "" "` {name} `\n" "added to start menu" msgstr "" #: luckyLUKS/setupUI.py:559 luckyLUKS/setupUI.py:566 msgid "" "Adding to start menu not possible,\n" "please place your shortcut manually.\n" "\n" "Desktop file saved to\n" "{location}" msgstr "" #: luckyLUKS/setupUI.py:587 msgid "" "Currently processing your request!\n" "Do you really want to quit?" msgstr "" #: luckyLUKS/setupUI.py:598 msgid "" "No tools to format the filesystem found\n" "Please install, eg for Debian/Ubuntu\n" "`apt-get install e2fslibs ntfs-3g`" msgstr "" #: luckyLUKS/setupUI.py:601 luckyLUKS/setupUI.py:655 msgid "Create" msgstr "" #: luckyLUKS/setupUI.py:606 msgid "Please choose a container file" msgstr "" #: luckyLUKS/setupUI.py:615 msgid "Please choose a folder as mountpoint" msgstr "" #: luckyLUKS/setupUI.py:621 msgid "Please choose a key file" msgstr "" #: luckyLUKS/setupUI.py:634 msgid "new_container.bin" msgstr "" #: luckyLUKS/setupUI.py:650 msgid "Please create a new file" msgstr "" #: luckyLUKS/setupUI.py:658 msgid "" "File already exists:\n" "{filename}\n" "\n" "Please create a new file!" msgstr "" #: luckyLUKS/setupUI.py:710 msgid "" "Enter the path of the new container file in the textbox or click " "the button next to the box for a graphical create file dialog.\n" "\n" "The device name will be used to identify the unlocked container. " "It can be any name up to 16 unicode characters, as long as it is unique." "\n" "\n" "The size of the container can be provided in GB or MB. The " "container will get initialized with random data, this can take quite a " "while - 1 hour for a 10GB container on an external drive is nothing " "unusual.\n" "\n" "To speed up container creation Quickformat can be enabled to use " "`fallocate` instead of initializing the container with random data - this" " means previous data will not be overwritten and some conclusions about " "encrypted data inside closed containers can be drawn.\n" msgstr "" #: luckyLUKS/setupUI.py:726 luckyLUKS/setupUI.py:767 msgid "" "A key file can be used to allow access to an encrypted container instead " "of a password. Using a key file resembles unlocking a door with a key in " "the real world - anyone with access to the key file can open your " "encrypted container. Make sure to store it at a protected location. Its " "okay to store it on your computer if you are using an already encrypted " "harddrive or a digital keystore. Having the key file on a small USB " "drive attached to your real chain of keys would be an option as well." "\n" "Since you dont have to enter a password, using a key file can be a " "convenient way to access your encrypted container. Just make sure you " "dont lose the key (file) ;)" msgstr "" #: luckyLUKS/setupUI.py:735 msgid "" "\n" "\n" "Although basically any file could be used as a key file, a file with " "predictable content leads to similar problems as using weak passwords. " "Audio files or pictures are a good choice. If unsure use the `create key " "file` button to generate a small key file filled with random data." msgstr "" #: luckyLUKS/setupUI.py:740 msgid "encryption format" msgstr "" #: luckyLUKS/setupUI.py:741 msgid "" "The standard disk encryption format on Linux is called LUKS. With LibreCrypt you can use " "LUKS containers on Windows as well. The TrueCrypt format is quite popular" " on Windows/Mac, and can be created on Linux if `tcplay` is installed. " "Please note, that \"hidden\" TrueCrypt partitions are not supported by " "luckyLUKS!" msgstr "" #: luckyLUKS/setupUI.py:748 msgid "" "Choose the ntfs filesystem to be able to access your data from Linux, " "Windows and Mac OSX. Since access permissions cannot be mapped from ntfs " "to Linux, access to ntfs devices is usually not restricted -> take care " "when using unlocked ntfs devices in a multiuser environment!" msgstr "" #: luckyLUKS/setupUI.py:759 msgid "" "Select the encrypted container file by clicking the button next to" " the textbox. Both LUKS and Truecrypt containers are supported!\n" "\n" "The device name will be used to identify the unlocked container. " "It can be any name up to 16 unicode characters, as long as it is unique " "-> you cannot give two unlocked containers the same name" msgstr "" #: luckyLUKS/setupUI.py:777 msgid "" "The mount point is the folder on your computer, where you can access the " "files inside the container after unlocking. If automatic mounting is " "configured on your system (eg with udisks), explicitly setting a " "mountpoint is not neccessary (but still possible)." msgstr "" #: luckyLUKS/unlockUI.py:122 msgid "" "Always allow luckyLUKS to be run\n" "with administrative privileges" msgstr "" #: luckyLUKS/unlockUI.py:153 msgid "" "Please choose a passphrase\n" "to encrypt the new container:\n" msgstr "" #: luckyLUKS/unlockUI.py:154 msgid "Enter new Passphrase" msgstr "" #: luckyLUKS/unlockUI.py:156 msgid "Display passphrase" msgstr "" #: luckyLUKS/unlockUI.py:189 msgid "" "Currently creating new container!\n" "Do you really want to quit?" msgstr "" #: luckyLUKS/unlockUI.py:220 msgid "" "Using keyfile\n" "{keyfile}\n" "to open container.\n" "\n" "Please wait .." msgstr "" #: luckyLUKS/unlockUI.py:254 msgid "Checking passphrase .." msgstr "" #: luckyLUKS/unlockUI.py:280 msgid "" "Please enter\n" "container passphrase:" msgstr "" #: luckyLUKS/unlockUI.py:282 msgid "" "Wrong passphrase, please retry!\n" "Enter container passphrase:" msgstr "" #: luckyLUKS/utils.py:64 msgid "" "Permanent `sudo` authorization for\n" "{program}\n" "has been successfully added for user `{username}` to \n" "/etc/sudoers.d/lucky-luks\n" msgstr "" #: luckyLUKS/utils.py:79 msgid "" "luckyLUKS needs administrative privileges.\n" "Please enter your password:" msgstr "" #: luckyLUKS/utils.py:98 luckyLUKS/utils.py:126 msgid "Sorry, incorrect password.\n" msgstr "" #: luckyLUKS/utils.py:114 msgid "" "You are not allowed to execute this script with `sudo`.\n" "If you want to modify your `sudo` configuration,\n" "please enter the root/administrator password.\n" msgstr "" #: luckyLUKS/utils.py:137 msgid "" "`sudo` configuration successfully modified, now\n" "you can use luckyLUKS with your user password.\n" "\n" "If you want to grant permanent administrative rights\n" "just tick the checkbox in the following dialog.\n" msgstr "" #: luckyLUKS/utils.py:162 msgid "" "Communication with sudo process failed\n" "{error}" msgstr "" #: luckyLUKS/utils.py:224 luckyLUKS/utils.py:232 luckyLUKS/utils.py:259 #: luckyLUKS/worker.py:130 msgid "" "Error in communication:\n" "{error}" msgstr "" #: luckyLUKS/utils.py:315 msgid "" "Error while creating key file:\n" "{error}" msgstr "" #: luckyLUKS/utilsUI.py:69 msgid "Advanced Topics:" msgstr "" #: luckyLUKS/utilsUI.py:93 msgid "" "luckyLUKS version {version}\n" "For more information, visit\n" "{project_url}" msgstr "" #: luckyLUKS/utilsUI.py:135 msgid "Error" msgstr "" #: luckyLUKS/worker.py:72 msgid "Please call with sudo." msgstr "" #: luckyLUKS/worker.py:75 msgid "" "Missing information of the calling user in sudo environment.\n" "Please make sure sudo is configured correctly." msgstr "" #: luckyLUKS/worker.py:121 msgid "Helper process received unknown command" msgstr "" #: luckyLUKS/worker.py:201 luckyLUKS/worker.py:454 msgid "Device Name is empty" msgstr "" #: luckyLUKS/worker.py:215 msgid "" "Could not use container:\n" "{file_path}\n" "{device_name} is already unlocked\n" "using a different container\n" "Please change the name to unlock this container" msgstr "" #: luckyLUKS/worker.py:231 msgid "" "Device Name too long:\n" "Only up to 16 characters possible, even less for unicode\n" "(roughly 8 non-ascii characters possible)" msgstr "" #: luckyLUKS/worker.py:238 luckyLUKS/worker.py:458 msgid "" "Illegal Device Name!\n" "Names starting with `-` or using `/` are not possible" msgstr "" #: luckyLUKS/worker.py:258 msgid "" "Cannot use the container\n" "{file_path}\n" "The container is already in use ({existing_device})." msgstr "" #: luckyLUKS/worker.py:270 luckyLUKS/worker.py:497 msgid "" "Key file not accessible\n" "or path does not exist:\n" "\n" "{file_path}" msgstr "" #: luckyLUKS/worker.py:279 msgid "" "Mount point not accessible\n" "or path does not exist:\n" "\n" "{mount_dir}" msgstr "" #: luckyLUKS/worker.py:284 msgid "" "Already mounted at mount point:\n" "\n" "{mount_dir}" msgstr "" #: luckyLUKS/worker.py:289 msgid "" "Designated mount directory\n" "{mount_dir}\n" "is not empty" msgstr "" #: luckyLUKS/worker.py:368 msgid "" "Open container failed.\n" "Please check key file" msgstr "" #: luckyLUKS/worker.py:404 msgid "Unable to close container, device is busy" msgstr "" #: luckyLUKS/worker.py:450 msgid "Container Filename not supplied" msgstr "" #: luckyLUKS/worker.py:462 msgid "" "Device Name too long:\n" "Only up to 16 characters possible, even less for unicode \n" "(roughly 8 non-ascii characters possible)" msgstr "" #: luckyLUKS/worker.py:467 msgid "" "Device Name already in use:\n" "\n" "{device_name}" msgstr "" #: luckyLUKS/worker.py:471 msgid "" "Container size too small\n" "to create encrypted filesystem\n" "Please choose at least 5MB" msgstr "" #: luckyLUKS/worker.py:485 msgid "" "Not enough free disc space for container:\n" "\n" "{space_needed} MB needed\n" "{space_available} MB available" msgstr "" #: luckyLUKS/worker.py:504 msgid "Unknown filesystem type: {filesystem_type}" msgstr "" #: luckyLUKS/worker.py:507 msgid "" "If you want to use TrueCrypt containers\n" "make sure `cryptsetup` is at least version 1.6 (`cryptsetup --version`)\n" "and `tcplay` is installed (eg for Debian/Ubuntu `apt-get install tcplay`)" msgstr "" #: luckyLUKS/worker.py:598 msgid "Unknown encryption format: {enc_fmt}" msgstr "" #: luckyLUKS/worker.py:653 msgid "Cannot change sudo rights, invalid username" msgstr "" #: luckyLUKS/worker.py:659 msgid "" "I`m afraid I can`t do that.\n" "\n" "To be able to permit permanent changes to sudo rights,\n" "please make sure the program is owned by root\n" "and not writeable by others.\n" "Execute the following commands in your shell:\n" "\n" "chmod 755 {program}\n" "sudo chown root:root {program}\n" "\n" msgstr "" luckyLUKS-2.1.0/luckyLUKS/main.py000066400000000000000000000127611420343070100164650ustar00rootroot00000000000000""" luckyLUKS is a GUI for creating and (un-)locking LUKS volumes from container files. For more information visit: http://github.com/jas-per/luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) This program 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. This program 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. """ import os.path import sys import argparse import types import builtins from luckyLUKS import VERSION_STRING, PROJECT_URL def luckyLUKS(translation, *args, **kwargs): """ main entry point: initialize gettext, parse arguments and start either GUI or worker """ translation.gettext_qt = types.MethodType(lambda self, msg: self.gettext(msg).replace('\n', '
'), translation) argparse._ = _ = translation.gettext # gettext for argsparse parser = argparse.ArgumentParser( description=_('GUI for creating and unlocking LUKS/TrueCrypt volumes from container files'), epilog=_('When called without any arguments a setup dialog will be shown before unlocking,\n' 'where you can select containerfile and name, or create a new encrypted container.\n' 'If both arguments are supplied, the unlock dialog will be shown directly.\n\n' 'Example:\n' ' {executable} -c /usbstick/encrypted.bin -n mydata -m /home/user/enc\n\n' 'If automatic mounting (eg udisks/polkit) is configured on your system,\n' 'explicitly setting a mountpoint is usually not needed (but still possible)\n\n' 'Homepage: {project_url}').format(executable=os.path.basename(sys.argv[0]), project_url=PROJECT_URL), formatter_class=argparse.RawDescriptionHelpFormatter ) # L10n: used by argsparse to generate help output on the console (luckyLUKS --help) ap_usage = _('usage: ') # L10n: used by argsparse to generate help output on the console (luckyLUKS --help) ap_optargs = _('optional arguments') # L10n: used by argsparse to generate help output on the console (luckyLUKS --help) ap_helpmsg = _('show this help message and exit') # L10n: used by argsparse to generate help output on the console (luckyLUKS --help) ap_errmsg = _('%(prog)s: error: %(message)s\n') # L10n: used by argsparse to generate help output on the console (luckyLUKS --help) ap_unknown = _('unrecognized arguments: %s') # if argument not specified or empty set value to None # error messages will be shown by the GUI, not on the command line parser.add_argument('-c', dest='container', nargs='?', metavar=_('PATH'), help=_('Path to the encrypted container file')) parser.add_argument('-n', dest='name', nargs='?', metavar=_('NAME'), help=_('Choose a device name to identify the unlocked container')) parser.add_argument('-m', dest='mountpoint', nargs='?', metavar=_('PATH'), help=_('Where to mount the encrypted filesystem')) parser.add_argument('-k', dest='keyfile', nargs='?', metavar=_('PATH'), help=_('Path to an optional key file')) parser.add_argument('-v', '--version', action='version', version="luckyLUKS " + VERSION_STRING, help=_("show program's version number and exit")) parser.add_argument('--ishelperprocess', action='store_true', help=argparse.SUPPRESS) parser.add_argument('--sudouser', type=int, help=argparse.SUPPRESS) parsed_args = parser.parse_args() # worker will be created by calling the script again (but this time with su privileges) builtins._ = translation.gettext_qt if parsed_args.ishelperprocess: startWorker(parsed_args.sudouser) else: startUI(parsed_args) def startUI(parsed_args): """ Import the required GUI elements and create main window """ from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import QLocale, QTranslator, QLibraryInfo from luckyLUKS.mainUI import MainWindow # l10n qt-gui elements qt_translator = QTranslator() qt_translator.load('qt_' + QLocale.system().name(), QLibraryInfo.location(QLibraryInfo.TranslationsPath)) application = QApplication(sys.argv) application.installTranslator(qt_translator) # start application main_win = MainWindow(parsed_args.name, parsed_args.container, parsed_args.keyfile, parsed_args.mountpoint) # setup OK -> run event loop if main_win.is_initialized: sys.exit(application.exec_()) else: sys.exit(0) def startWorker(sudouser=None): """ Initialize worker process """ from luckyLUKS import worker if sudouser is not None: # helper called with su to configure sudo if os.getuid() == 0 and os.getenv("SUDO_UID") is None: try: worker.WorkerHelper().modify_sudoers(sudouser) sys.exit(0) except worker.WorkerException as we: sys.stdout.write(str(we)) sys.exit(2) else: # deny giving other user userids sudo access to luckyLUKS if not called with su sys.exit(2) else: worker.run() luckyLUKS-2.1.0/luckyLUKS/mainUI.py000066400000000000000000000417261420343070100167260ustar00rootroot00000000000000""" This module contains the main window of the application luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) This program 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. This program 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. """ import sys import os.path from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QDesktopWidget, QDialog,\ QSystemTrayIcon, QMessageBox, QMenu, QAction, QLabel, QPushButton, QGridLayout, QStyle from PyQt5.QtGui import QIcon from luckyLUKS import utils, PROJECT_URL from luckyLUKS.unlockUI import UnlockContainerDialog, UserInputError from luckyLUKS.utilsUI import show_alert class MainWindow(QMainWindow): """ A window that shows the current status of the encrypted container and a button to unlock/close it. Open containers leave an icon in the systray as a reminder to close them eventually. """ def __init__(self, device_name=None, container_path=None, key_file=None, mount_point=None): """ Command line arguments checks are done here to be able to display a graphical dialog with error messages . If no arguments were supplied on the command line a setup dialog will be shown. All commands will be executed from a separate worker process with administrator privileges that gets initialized here. :param device_name: The device mapper name :type device_name: str/unicode or None :param container_path: The path of the container file :type container_path: str/unicode or None :param key_file: The path of an optional key file :type key_file: str/unicode or None :param mount_point: The path of an optional mount point :type mount_point: str/unicode or None """ super().__init__() self.luks_device_name = device_name self.encrypted_container = container_path self.key_file = key_file self.mount_point = mount_point self.worker = None self.is_waiting_for_worker = False self.is_unlocked = False self.is_initialized = False self.has_tray = QSystemTrayIcon.isSystemTrayAvailable() # L10n: program name - translatable for startmenu titlebar etc self.setWindowTitle(_('luckyLUKS')) self.setWindowIcon(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon))) # check if cryptsetup and sudo are installed not_installed_msg = _('{program_name} executable not found!\n' 'Please install, eg for Debian/Ubuntu\n`apt-get install {program_name}`') if not utils.is_installed('cryptsetup'): show_alert(self, not_installed_msg.format(program_name='cryptsetup'), critical=True) if not utils.is_installed('sudo'): show_alert(self, not_installed_msg.format(program_name='sudo'), critical=True) # quick sanity checks before asking for passwd if os.getuid() == 0: show_alert(self, _('Graphical programs should not be run as root!\nPlease call as normal user.'), critical=True ) if self.encrypted_container and not os.path.exists(self.encrypted_container): show_alert( self, _('Container file not accessible\nor path does not exist:\n\n{file_path}') .format(file_path=self.encrypted_container), critical=True ) # only either encrypted_container or luks_device_name supplied if bool(self.encrypted_container) != bool(self.luks_device_name): show_alert(self, _('Invalid arguments:\n' 'Please call without any arguments\n' 'or supply both container and name.\n\n' '{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]\n\n' 'CONTAINER = Path of the encrypted container file\n' 'NAME = A (unique) name to identify the unlocked container\n' 'Optional: MOUNTPOINT = where to mount the encrypted filesystem\n\n' 'If automatic mounting is configured on your system,\n' 'explicitly setting a mountpoint is not required\n\n' 'For more information, visit\n' '{project_url}' ).format(executable=os.path.basename(sys.argv[0]), project_url=PROJECT_URL), critical=True) # spawn worker process with root privileges try: self.worker = utils.WorkerMonitor(self) # start communication thread self.worker.start() except utils.SudoException as se: show_alert(self, str(se), critical=True) return # if no arguments supplied, display dialog to gather this information if self.encrypted_container is None and self.luks_device_name is None: from luckyLUKS.setupUI import SetupDialog sd = SetupDialog(self) if sd.exec_() == QDialog.Accepted: self.luks_device_name = sd.get_luks_device_name() self.encrypted_container = sd.get_encrypted_container() self.mount_point = sd.get_mount_point() self.key_file = sd.get_keyfile() self.is_unlocked = True # all checks in setup dialog -> skip initializing state else: # user closed dialog -> quit program # and check if a keyfile create thread has to be stopped # the worker process terminates itself when its parent dies if hasattr(sd, 'create_thread') and sd.create_thread is not None and sd.create_thread.isRunning(): sd.create_thread.terminate() QApplication.instance().quit() return # center window on desktop qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) # widget content main_grid = QGridLayout() main_grid.setSpacing(10) icon = QLabel() icon.setPixmap(QIcon.fromTheme( 'dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon) ).pixmap(32)) main_grid.addWidget(icon, 0, 0) main_grid.addWidget(QLabel('' + _('Handle encrypted container') + '\n'), 0, 1, alignment=Qt.AlignCenter) main_grid.addWidget(QLabel(_('Name:')), 1, 0) main_grid.addWidget(QLabel('{dev_name}'.format(dev_name=self.luks_device_name)), 1, 1, alignment=Qt.AlignCenter) main_grid.addWidget(QLabel(_('File:')), 2, 0) main_grid.addWidget(QLabel(self.encrypted_container), 2, 1, alignment=Qt.AlignCenter) if self.key_file is not None: main_grid.addWidget(QLabel(_('Key:')), 3, 0) main_grid.addWidget(QLabel(self.key_file), 3, 1, alignment=Qt.AlignCenter) if self.mount_point is not None: main_grid.addWidget(QLabel(_('Mount:')), 4, 0) main_grid.addWidget(QLabel(self.mount_point), 4, 1, alignment=Qt.AlignCenter) main_grid.addWidget(QLabel(_('Status:')), 5, 0) self.label_status = QLabel('') main_grid.addWidget(self.label_status, 5, 1, alignment=Qt.AlignCenter) self.button_toggle_status = QPushButton('') self.button_toggle_status.setMinimumHeight(34) self.button_toggle_status.clicked.connect(self.toggle_container_status) main_grid.setRowMinimumHeight(6, 10) main_grid.addWidget(self.button_toggle_status, 7, 1) widget = QWidget() widget.setLayout(main_grid) widget.setContentsMargins(10, 10, 10, 10) self.setCentralWidget(widget) # tray popup menu if self.has_tray: tray_popup = QMenu(self) tray_popup.addAction(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)), self.luks_device_name).setEnabled(False) tray_popup.addSeparator() self.tray_toggle_action = QAction(QApplication.style().standardIcon(QStyle.SP_DesktopIcon), _('Hide'), self) self.tray_toggle_action.triggered.connect(self.toggle_main_window) tray_popup.addAction(self.tray_toggle_action) quit_action = QAction(QApplication.style().standardIcon(QStyle.SP_MessageBoxCritical), _('Quit'), self) quit_action.triggered.connect(self.tray_quit) tray_popup.addAction(quit_action) # systray self.tray = QSystemTrayIcon(self) self.tray.setIcon(QIcon.fromTheme( 'dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)) ) self.tray.setContextMenu(tray_popup) self.tray.activated.connect(self.toggle_main_window) self.tray.show() self.init_status() def refresh(self): """ Update widgets to reflect current container status. Adds systray icon if needed """ if self.is_unlocked: self.label_status.setText(_('Container is {unlocked_green_bold}').format( unlocked_green_bold='' + _('unlocked') + '')) self.button_toggle_status.setText(_('Close Container')) if self.has_tray: self.tray.setToolTip(_('{device_name} is unlocked').format(device_name=self.luks_device_name)) else: self.label_status.setText(_('Container is {closed_red_bold}').format( closed_red_bold='' + _('closed') + '')) self.button_toggle_status.setText(_('Unlock Container')) if self.has_tray: self.tray.setToolTip(_('{device_name} is closed').format(device_name=self.luks_device_name)) self.show() self.setFixedSize(self.sizeHint()) def tray_quit(self): """ Triggered by clicking on `quit` in the systray popup: asks to close an unlocked container """ if not self.is_unlocked: QApplication.instance().quit() elif not self.is_waiting_for_worker: self.show() self.confirm_close() def toggle_main_window(self, tray_icon_clicked): """ Triggered by clicking on the systray icon: show/hide main window """ # don't activate on rightclick/contextmenu if not tray_icon_clicked or tray_icon_clicked == QSystemTrayIcon.Trigger: if self.isVisible(): self.hide() self.tray_toggle_action.setText(_('Show')) else: self.show() self.tray_toggle_action.setText(_('Hide')) def closeEvent(self, event): """ Triggered by closing the window: If the container is unlocked, the program won't quit but remain in the systray. """ if not self.is_waiting_for_worker: if self.is_unlocked: if self.has_tray: self.hide() self.tray_toggle_action.setText(_('Show')) else: self.confirm_close() event.ignore() else: event.accept() def confirm_close(self): """ Inform about opened container and ask for confirmation to close & quit """ message = _('{device_name} >> {container_path}\n' 'is currently unlocked,\n' 'Close Container now and quit?').format(device_name=self.luks_device_name, container_path=self.encrypted_container) mb = QMessageBox(QMessageBox.Question, '', message, QMessageBox.Ok | QMessageBox.Cancel, self) mb.button(QMessageBox.Ok).setText(_('Quit')) if mb.exec_() == QMessageBox.Ok: self.do_close_container(shutdown=True) def customEvent(self, event): """ Receives response from worker and calls supplied callback function """ event.callback(event.response) def toggle_container_status(self): """ Unlock or close container """ if self.is_unlocked: self.do_close_container() else: try: UnlockContainerDialog( self, self.worker, self.luks_device_name, self.encrypted_container, self.key_file, self.mount_point ).communicate() self.is_unlocked = True except UserInputError as uie: show_alert(self, str(uie)) self.is_unlocked = False self.refresh() def do_close_container(self, shutdown=False): """ Send close command to worker and supply callbacks :param shutdown: Quit application after container successfully closed? (default=False) :type shutdown: bool """ self.disable_ui(_('Closing Container ..')) self.worker.execute(command={'type': 'request', 'msg': 'close', 'device_name': self.luks_device_name, 'container_path': self.encrypted_container }, success_callback=lambda msg: self.on_container_closed(msg, error=False, shutdown=shutdown), error_callback=lambda msg: self.on_container_closed(msg, error=True, shutdown=shutdown)) def on_container_closed(self, message, error, shutdown): """ Callback after worker closed container :param message: Contains an error description if error=True, otherwise the current state of the container (unlocked/closed) :type message: str :param error: Error during closing of container :type error: bool :param shutdown: Quit application after container successfully closed? :type shutdown: bool """ if error: show_alert(self, message) else: self.is_unlocked = False if not error and shutdown: # automatic shutdown only if container successfully closed QApplication.instance().quit() else: self.enable_ui() def init_status(self): """ Request current status of container from worker if needed """ if not self.is_unlocked: self.disable_ui(_('Initializing ..')) self.worker.execute(command={'type': 'request', 'msg': 'status', 'device_name': self.luks_device_name, 'container_path': self.encrypted_container, 'key_file': self.key_file, 'mount_point': self.mount_point }, success_callback=self.on_initialized, error_callback=lambda msg: self.on_initialized(msg, error=True)) else: # unlocked by setup-dialog -> just refresh UI self.enable_ui() self.is_initialized = True # qt event loop can start now def on_initialized(self, message, error=False): """ Callback after worker send current state of container :param message: Contains an error description if error=True, otherwise the current state of the container (unlocked/closed) :type message: str :param critical: Error during initialization (default=False) :type critical: bool """ if error: show_alert(self, message, critical=True) else: self.is_unlocked = bool(message == 'unlocked') self.enable_ui() def enable_ui(self): """ Enable buttons and refresh state """ self.refresh() self.is_waiting_for_worker = False self.button_toggle_status.setEnabled(True) def disable_ui(self, reason): """ Disable buttons and display waiting message :param reason: A waiting message that gets displayed :type reason: str/unicode """ self.is_waiting_for_worker = True self.button_toggle_status.setText(reason) self.button_toggle_status.setEnabled(False) luckyLUKS-2.1.0/luckyLUKS/setupUI.py000066400000000000000000001167741420343070100171500ustar00rootroot00000000000000""" If the main program gets called without arguments, this GUI will be shown first. luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) This program 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. This program 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. """ import os import sys import codecs import random import string import subprocess import glob from shutil import move from PyQt5.QtCore import QTimer, Qt from PyQt5.QtWidgets import QDialog, QVBoxLayout, QTabWidget, QDialogButtonBox, QGridLayout, QLabel, QStackedWidget,\ QMessageBox, QLineEdit, QPushButton, QSpinBox, QComboBox, QCheckBox, QFileDialog, QWidget, QStyle, QApplication,\ QProgressBar, QLayout from luckyLUKS.unlockUI import FormatContainerDialog, UnlockContainerDialog, UserInputError from luckyLUKS.utilsUI import QExpander, HelpDialog, show_info, show_alert from luckyLUKS.utils import is_installed class SetupDialog(QDialog): """ This dialog consists of two parts/tabs: The first one is supposed to help choosing container, device name and mount point to unlock an existing container. The second tab assists in creating a new encrypted LUKS container. """ def __init__(self, parent): """ :param parent: The parent window/dialog used to enable modal behaviour :type parent: :class:`PyQt5.QtGui.QWidget` """ super().__init__(parent, Qt.WindowCloseButtonHint | Qt.WindowTitleHint) self.setWindowTitle(_('luckyLUKS')) self.worker = parent.worker self.is_busy = False # build ui self.layout = QVBoxLayout() self.layout.setSizeConstraint(QLayout.SetFixedSize) style = QApplication.style() # set up stacked layout: initially only the main tab pane (unlock/create) self.main_pane = QStackedWidget() self.tab_pane = QTabWidget() self.main_pane.addWidget(self.tab_pane) self.layout.addWidget(self.main_pane) self.create_pane = None # init in on_create_container() # Unlock Tab unlock_grid = QGridLayout() unlock_grid.setColumnMinimumWidth(1, 220) uheader = QLabel(_('Unlock an encrypted container\n') + _('Please select container file and name')) uheader.setContentsMargins(0, 10, 0, 10) unlock_grid.addWidget(uheader, 0, 0, 1, 3, Qt.AlignCenter) label = QLabel(_('container file')) label.setIndent(5) unlock_grid.addWidget(label, 1, 0) self.unlock_container_file = QLineEdit() unlock_grid.addWidget(self.unlock_container_file, 1, 1) button_choose_file = QPushButton(style.standardIcon(QStyle.SP_DialogOpenButton), '', self) button_choose_file.setToolTip(_('choose file')) unlock_grid.addWidget(button_choose_file, 1, 2) button_choose_file.clicked.connect(self.on_select_container_clicked) label = QLabel(_('device name')) label.setIndent(5) unlock_grid.addWidget(label, 2, 0) self.unlock_device_name = QLineEdit() unlock_grid.addWidget(self.unlock_device_name, 2, 1) # advanced settings a_settings = QExpander(_('Advanced'), self, False) unlock_grid.addWidget(a_settings, 3, 0, 1, 3) label = QLabel(_('key file')) label.setIndent(5) unlock_grid.addWidget(label, 4, 0) self.unlock_keyfile = QLineEdit() unlock_grid.addWidget(self.unlock_keyfile, 4, 1) button_choose_uKeyfile = QPushButton(style.standardIcon(QStyle.SP_DialogOpenButton), '') button_choose_uKeyfile.setToolTip(_('choose keyfile')) unlock_grid.addWidget(button_choose_uKeyfile, 4, 2) button_choose_uKeyfile.clicked.connect(lambda: self.on_select_keyfile_clicked('Unlock')) a_settings.addWidgets([unlock_grid.itemAtPosition(4, column).widget() for column in range(0, 3)]) label = QLabel(_('mount point')) label.setIndent(5) unlock_grid.addWidget(label, 5, 0) self.unlock_mountpoint = QLineEdit() unlock_grid.addWidget(self.unlock_mountpoint, 5, 1) button_choose_mountpoint = QPushButton(style.standardIcon(QStyle.SP_DialogOpenButton), '') button_choose_mountpoint.setToolTip(_('choose folder')) unlock_grid.addWidget(button_choose_mountpoint, 5, 2) button_choose_mountpoint.clicked.connect(self.on_select_mountpoint_clicked) a_settings.addWidgets([unlock_grid.itemAtPosition(5, column).widget() for column in range(0, 3)]) unlock_grid.setRowStretch(6, 1) unlock_grid.setRowMinimumHeight(6, 10) button_help_unlock = QPushButton(style.standardIcon(QStyle.SP_DialogHelpButton), _('Help')) button_help_unlock.clicked.connect(self.show_help_unlock) unlock_grid.addWidget(button_help_unlock, 7, 2) unlock_tab = QWidget() unlock_tab.setLayout(unlock_grid) self.tab_pane.addTab(unlock_tab, _('Unlock Container')) # Create Tab create_grid = QGridLayout() cheader = QLabel(_('Create a new encrypted container\n') + _('Please choose container file, name and size')) cheader.setContentsMargins(0, 10, 0, 10) create_grid.addWidget(cheader, 0, 0, 1, 3, Qt.AlignCenter) label = QLabel(_('container file')) label.setIndent(5) create_grid.addWidget(label, 1, 0) self.create_container_file = QLineEdit() create_grid.addWidget(self.create_container_file, 1, 1) button_choose_file = QPushButton(style.standardIcon(QStyle.SP_DialogOpenButton), '') button_choose_file.setToolTip(_('set file')) create_grid.addWidget(button_choose_file, 1, 2) button_choose_file.clicked.connect(self.on_save_container_clicked) label = QLabel(_('device name')) label.setIndent(5) create_grid.addWidget(label, 2, 0) self.create_device_name = QLineEdit() create_grid.addWidget(self.create_device_name, 2, 1) label = QLabel(_('container size')) label.setIndent(5) create_grid.addWidget(label, 3, 0) self.create_container_size = QSpinBox() self.create_container_size.setRange(1, 1000000000) self.create_container_size.setValue(1) create_grid.addWidget(self.create_container_size, 3, 1) self.create_size_unit = QComboBox() self.create_size_unit.addItems(['MB', 'GB']) self.create_size_unit.setCurrentIndex(1) create_grid.addWidget(self.create_size_unit, 3, 2) self.create_quickformat = QCheckBox(_('Quickformat')) create_grid.addWidget(self.create_quickformat, 4, 1) # advanced settings a_settings = QExpander(_('Advanced'), self, False) create_grid.addWidget(a_settings, 5, 0, 1, 3) label = QLabel(_('key file')) label.setIndent(5) create_grid.addWidget(label, 6, 0) self.create_keyfile = QLineEdit() create_grid.addWidget(self.create_keyfile, 6, 1) button_choose_cKeyfile = QPushButton(style.standardIcon(QStyle.SP_DialogOpenButton), '') button_choose_cKeyfile.setToolTip(_('choose keyfile')) create_grid.addWidget(button_choose_cKeyfile, 6, 2) button_choose_cKeyfile.clicked.connect(lambda: self.on_select_keyfile_clicked('Create')) a_settings.addWidgets([create_grid.itemAtPosition(6, column).widget() for column in range(0, 3)]) button_create_keyfile = QPushButton(_('Create key file')) button_create_keyfile.clicked.connect(self.on_create_keyfile) create_grid.addWidget(button_create_keyfile, 7, 1) a_settings.addWidgets([button_create_keyfile]) label = QLabel(_('format')) label.setIndent(5) create_grid.addWidget(label, 8, 0) self.create_encryption_format = QComboBox() self.create_encryption_format.addItem('LUKS') self.create_encryption_format.addItem('TrueCrypt') if not is_installed('tcplay'): self.create_encryption_format.setEnabled(False) self.create_encryption_format.setCurrentIndex(0) create_grid.addWidget(self.create_encryption_format, 8, 1) a_settings.addWidgets([create_grid.itemAtPosition(8, column).widget() for column in range(0, 2)]) label = QLabel(_('filesystem')) label.setIndent(5) create_grid.addWidget(label, 9, 0) filesystems = ['ext4', 'ext2', 'ntfs'] self.create_filesystem_type = QComboBox() for filesystem in filesystems: if is_installed('mkfs.' + filesystem): self.create_filesystem_type.addItem(filesystem) self.create_filesystem_type.setCurrentIndex(0) create_grid.addWidget(self.create_filesystem_type, 9, 1) a_settings.addWidgets([create_grid.itemAtPosition(9, column).widget() for column in range(0, 2)]) create_grid.setRowStretch(10, 1) create_grid.setRowMinimumHeight(10, 10) button_help_create = QPushButton(style.standardIcon(QStyle.SP_DialogHelpButton), _('Help')) button_help_create.clicked.connect(self.show_help_create) create_grid.addWidget(button_help_create, 11, 2) create_tab = QWidget() create_tab.setLayout(create_grid) self.tab_pane.addTab(create_tab, _('Create New Container')) self.tab_pane.currentChanged.connect(self.on_switchpage_event) # OK and Cancel buttons self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, parent=self) self.buttons.button(QDialogButtonBox.Ok).setText(_('Unlock')) self.buttons.accepted.connect(self.on_accepted) self.buttons.rejected.connect(self.reject) self.layout.addWidget(self.buttons) # ui built, add to widget self.setLayout(self.layout) # var-def for create-subdialog self.create_progressbars = [] self.create_thread = None self.create_status_grid = None self.create_timer = None def on_create_container(self): """ Triggered by clicking create. Hides the unlock/create pane and switches to a status pane where the progress in creating the new container can be followed step by step. This shows the header and the first step: Initializing the container file with random data """ self.init_create_pane() header = QLabel(_('Creating new container\n') + _('patience .. this might take a while')) header.setContentsMargins(0, 10, 0, 10) self.create_status_grid.addWidget(header, 0, 0, 1, 3, Qt.AlignCenter) self.create_status_grid.addWidget(QLabel('' + _('Step') + ' 1/3'), 1, 0) self.create_status_grid.addWidget(QLabel(_('Initializing Container File')), 1, 1) self.create_progressbars.append(QProgressBar()) self.create_progressbars[0].setRange(0, 100) self.create_status_grid.addWidget(self.create_progressbars[0], 2, 0, 1, 3) self.create_status_grid.setRowStretch(7, 1) # top align # add to stack widget and switch display self.main_pane.addWidget(self.create_pane) self.main_pane.setCurrentIndex(1) # calculate designated container size for worker and progress indicator size = self.create_container_size.value() size = size * (1024 * 1024 * 1024 if self.create_size_unit.currentIndex() == 1 else 1024 * 1024) # GB vs MB location = self.create_container_file.text().strip() if not os.path.dirname(location): location = os.path.join(os.path.expanduser('~'), location) self.create_container_file.setText(location) keyfile = self.create_keyfile.text().strip() if self.create_keyfile.text().strip() != '' else None # start timer for progressbar updates during container creation self.create_timer.timeout.connect(lambda: self.display_progress_percent(location, size)) self.create_timer.start(500) self.worker.execute(command={'type': 'request', 'msg': 'create', 'device_name': self.create_device_name.text().strip(), 'container_path': location, 'container_size': size, 'quickformat': self.create_quickformat.isChecked(), 'key_file': keyfile, 'filesystem_type': str(self.create_filesystem_type.currentText()), 'encryption_format': str(self.create_encryption_format.currentText()), }, success_callback=self.on_luksFormat_prompt, error_callback=lambda msg: self.display_create_failed(msg, stop_timer=True)) def on_luksFormat_prompt(self, msg): """ Triggered after the container file is created on disk Shows information about the next step and asks the user for the passphrase to be used with the new container """ self.set_progress_done(self.create_timer, self.create_progressbars[0]) self.create_status_grid.addWidget(QLabel('' + _('Step') + ' 2/3'), 3, 0) self.create_status_grid.addWidget(QLabel(_('Initializing Encryption')), 3, 1) self.create_progressbars.append(QProgressBar()) self.create_progressbars[1].setRange(0, 0) self.create_status_grid.addWidget(self.create_progressbars[1], 4, 0, 1, 3) if msg == 'getPassword': try: self.worker.execute(command={'type': 'response', 'msg': FormatContainerDialog(self).get_password() }, success_callback=self.on_creating_filesystem, error_callback=self.display_create_failed) except UserInputError: # user cancelled dlg self.worker.execute({'type': 'abort', 'msg': ''}, None, None) # notify worker process self.display_create_failed(_('Initialize container aborted')) else: # using keyfile self.worker.execute(command={'type': 'response', 'msg': ''}, success_callback=self.on_creating_filesystem, error_callback=self.display_create_failed) def on_creating_filesystem(self, msg): """ Triggered after LUKS encryption got initialized. Shows information about the last step """ self.set_progress_done(progressbar=self.create_progressbars[1]) self.create_status_grid.addWidget(QLabel('' + _('Step') + ' 3/3'), 5, 0) self.create_status_grid.addWidget(QLabel(_('Initializing Filesystem')), 5, 1) self.create_progressbars.append(QProgressBar()) self.create_progressbars[2].setRange(0, 0) self.create_status_grid.addWidget(self.create_progressbars[2], 6, 0, 1, 3) self.worker.execute(command={'type': 'response', 'msg': ''}, success_callback=self.display_create_success, error_callback=self.display_create_failed) def display_create_success(self, msg): """ Triggered after successful creation of a new container """ self.set_progress_done(progressbar=self.create_progressbars[2]) # copy values of newly created container to unlock dlg und reset create values self.unlock_container_file.setText(self.create_container_file.text()) self.unlock_device_name.setText(self.create_device_name.text()) self.unlock_keyfile.setText(self.create_keyfile.text()) show_info(self, _('{device_name}\nsuccessfully created!\nClick on unlock to use the new container') .format(device_name=self.create_device_name.text().strip()), _('Success')) # reset create ui and switch to unlock tab self.create_container_file.setText('') self.create_device_name.setText('') self.create_container_size.setValue(1) self.create_size_unit.setCurrentIndex(1) self.create_keyfile.setText('') self.create_encryption_format.setCurrentIndex(0) self.create_filesystem_type.setCurrentIndex(0) self.display_create_done() self.tab_pane.setCurrentIndex(0) def display_create_failed(self, errormessage, stop_timer=False): """ Triggered when an error happend during the create process :param errormessage: errormessage to be shown :type errormessage: str :param stop_timer: stop a progress indicator? :type stop_timer: bool """ if stop_timer: self.set_progress_done(self.create_timer) show_alert(self, errormessage) self.display_create_done() def display_create_done(self): """ Helper to hide the create process informations and show the unlock/create pane """ self.is_busy = False self.main_pane.setCurrentIndex(0) self.buttons.setEnabled(True) def set_progress_done(self, timeout=None, progressbar=None): """ Helper to end stop the progress indicator :param timeout: Timer to stop :type timeout: QTimer or None :param progressbar: progressbar widget to set to 'Done' :type progressbar: :class:`QProgressBar` or None """ if timeout is not None: timeout.stop() if progressbar is not None: if not progressbar.maximum(): progressbar.setRange(0, 100) progressbar.setValue(100) progressbar.setFormat(_('Done')) def on_accepted(self): """ Event handler for response: Start unlock or create action """ try: if self.tab_pane.currentIndex() == 1: self.on_create_container() else: UnlockContainerDialog(self, self.worker, self.get_luks_device_name(), self.get_encrypted_container(), self.get_keyfile(), self.get_mount_point() ).communicate() # blocks # optionally create startmenu entry self.show_create_startmenu_entry() # all good, now switch to main window self.accept() except UserInputError as error: show_alert(self, str(error)) def init_create_pane(self): """ Helper that initializes the ui for the progress indicators shown while creating containers or keyfiles """ self.is_busy = True self.create_progressbars = [] self.create_timer = QTimer(self) self.buttons.setEnabled(False) if self.main_pane.count() > 1: # remove previous create display if any self.main_pane.removeWidget(self.create_pane) # built ui self.create_pane = QWidget() self.create_status_grid = QGridLayout() self.create_pane.setLayout(self.create_status_grid) self.create_status_grid.setVerticalSpacing(5) def on_create_keyfile(self): """ Triggered by clicking the `create key file` button below the key file text field (create) Asks for key file location if not already provided, creates the progress ui and starts a create-thread """ if self.create_keyfile.text() == '': key_file = self.on_save_file(_('new_keyfile.bin')).strip() else: key_file = self.create_keyfile.text().strip() if not os.path.dirname(key_file): key_file = os.path.join(os.path.expanduser('~'), key_file) self.init_create_pane() header = QLabel(_('Creating key file')) self.create_status_grid.addWidget(header, 1, 0, 1, 3, Qt.AlignCenter) header.setContentsMargins(0, 30, 0, 10) self.create_progressbars.append(QProgressBar()) self.create_progressbars[0].setRange(0, 100) self.create_status_grid.addWidget(self.create_progressbars[0], 2, 0, 1, 3) info = QLabel(_('This might take a while. Since computers are deterministic machines\n' 'it is quite a challenge to generate real random data for the key.\n' '\n' 'You can speed up the process by typing, moving the mouse\n' 'and generally use the computer while the key gets generated.')) info.setContentsMargins(0, 10, 0, 10) self.create_status_grid.addWidget(info, 3, 0, 1, 3, Qt.AlignCenter) self.create_status_grid.setRowStretch(4, 2) # vertical align # add to stack widget and switch display self.main_pane.addWidget(self.create_pane) self.main_pane.setCurrentIndex(1) # start timer for progressbar updates during keyfile creation self.create_timer.timeout.connect(lambda: self.display_progress_percent(key_file, 1024)) self.create_timer.start(500) # run QThread with keyfile creation from luckyLUKS.utils import KeyfileCreator self.create_thread = KeyfileCreator(self, key_file) self.create_thread.start() def on_keyfile_created(self, key_file_path): """ Triggered when key file creation was successful. Restores the normal setup ui """ self.set_progress_done(self.create_timer, progressbar=self.create_progressbars[0]) show_info(self, _('{key_file}\nsuccessfully created!\n' 'You can use this key file now,\n' 'to create a new container.').format(key_file=key_file_path), _('Success')) self.display_create_done() self.create_keyfile.setText(key_file_path) def show_create_startmenu_entry(self): """ Shown after successfull unlock with setup dialog -> ask for shortcut creation """ message = (_('Successfully unlocked!\n\n' 'Do you want to create\n' 'a startup menu entry for {device_name}?\n\n' '-> Your password will NOT be saved!\n' ' This just creates a shortcut,\n' ' to the unlock container dialog.\n').format( device_name=self.get_luks_device_name()) ) mb = QMessageBox(QMessageBox.Question, '', message, QMessageBox.Ok | QMessageBox.Cancel, self) mb.button(QMessageBox.Ok).setText(_('Create shortcut')) mb.button(QMessageBox.Cancel).setText(_('No, thanks')) if mb.exec_() == QMessageBox.Ok: self.create_startmenu_entry() def create_startmenu_entry(self): """ Creates a startmenu entry that lets the user skip the setup dialog and go directly to the main UI Includes a workaround for the safety net some desktop environments create around the startupmenu """ # command to be saved in shortcut: calling the script with the arguments entered in the dialog # put all arguments in single quotes and escape those in the strings (shell escape ' -> '\'') cmd = os.path.abspath(sys.argv[0]) cmd += " -c '" + self.get_encrypted_container().replace("'", "'\\'''") + "'" cmd += " -n '" + self.get_luks_device_name().replace("'", "'\\'''") + "'" if self.get_mount_point() is not None: cmd += " -m '" + self.get_mount_point().replace("'", "'\\'''") + "'" if self.get_keyfile() is not None: cmd += " -k '" + self.get_keyfile().replace("'", "'\\'''") + "'" # create .desktop-file # xdg-desktop-menu has problems does not like some special chars filename = _('luckyLUKS') + '-' + ''.join(i for i in self.get_luks_device_name() if i not in ' \\/:*?<>|') if is_installed('xdg-desktop-menu'): # create in tmp and add freedesktop menu entry # some desktop menus dont delete the .desktop files if a user removes # items from the menu but keep track of those files instead and do not readded later # -> the random part of the filename works around this behaviour rand4 = ''.join(random.choice(string.ascii_letters) for i in range(4)) desktop_file_path = filename + '-' + rand4 + '.desktop' desktop_file_path = os.path.join('/tmp', desktop_file_path) else: # or create in users home dir desktop_file_path = os.path.join(os.path.expanduser('~'), filename + '.desktop') desktop_file = codecs.open(desktop_file_path, 'w', 'utf-8') entry_name = _('Unlock {device_name}').format(device_name=self.get_luks_device_name()) desktop_file.write("[Desktop Entry]\n") desktop_file.write("Name=" + entry_name + "\n") desktop_file.write("Comment=" + self.get_luks_device_name() + " " + _('Encrypted Container Tool') + "\n") desktop_file.write("GenericName=" + _('Encrypted Container') + "\n") desktop_file.write("Categories=Utility;\n") desktop_file.write("Exec=" + cmd + "\n") desktop_file.write("Icon=dialog-password\n") desktop_file.write("NoDisplay=false\n") desktop_file.write("StartupNotify=false\n") desktop_file.write("Terminal=0\n") desktop_file.write("TerminalOptions=\n") desktop_file.write("Type=Application\n\n") desktop_file.close() os.chmod(desktop_file_path, 0o700) # some distros need the xbit to trust the desktop file if is_installed('xdg-desktop-menu'): # safest way to ensure updates: explicit uninstall # followed by installing a new desktop file with different random part desktopfiles = os.path.expanduser('~') + '/.local/share/applications/' + filename + '-*.desktop' for desktopfile in glob.glob(desktopfiles): with open(os.devnull) as DEVNULL: subprocess.call( ['xdg-desktop-menu', 'uninstall', desktopfile], stdout=DEVNULL, stderr=subprocess.STDOUT) try: subprocess.check_output( ['xdg-desktop-menu', 'install', '--novendor', desktop_file_path], stderr=subprocess.STDOUT, universal_newlines=True) os.remove(desktop_file_path) # remove from tmp show_info(self, _('` {name} `\nadded to start menu').format(name=entry_name), _('Success')) except subprocess.CalledProcessError as cpe: home_dir_path = os.path.join(os.path.expanduser('~'), os.path.basename(desktop_file_path)) # move to homedir instead move(desktop_file_path, home_dir_path) show_alert(self, cpe.output) show_info( self, _('Adding to start menu not possible,\n' 'please place your shortcut manually.\n\nDesktop file saved to\n{location}') .format(location=home_dir_path) ) else: show_info( self, _('Adding to start menu not possible,\n' 'please place your shortcut manually.\n\nDesktop file saved to\n{location}') .format(location=desktop_file_path) ) def reject(self): """ Event handler cancel: Ask for confirmation while creating container """ if self.confirm_close(): super().reject() def closeEvent(self, event): """ Event handler close: ask for confirmation while creating container """ if not self.confirm_close(): event.ignore() def confirm_close(self): """ Displays a confirmation dialog if currently busy creating container or keyfile :returns: The users decision or True if no create process running :rtype: bool """ if self.is_busy: message = _('Currently processing your request!\nDo you really want to quit?') mb = QMessageBox(QMessageBox.Question, '', message, QMessageBox.Ok | QMessageBox.Cancel, self) mb.button(QMessageBox.Ok).setText(_('Quit')) return mb.exec_() == QMessageBox.Ok return True def on_switchpage_event(self, index): """ Event handler for tab switch: change text on OK button (Unlock/Create) """ new_ok_label = _('Unlock') if index == 1: # create if self.create_filesystem_type.currentText() == '': show_alert(self, _('No tools to format the filesystem found\n' 'Please install, eg for Debian/Ubuntu\n' '`apt-get install e2fslibs ntfs-3g`')) new_ok_label = _('Create') self.buttons.button(QDialogButtonBox.Ok).setText(new_ok_label) def on_select_container_clicked(self): """ Triggered by clicking the select button next to container file (unlock) """ file_path = QFileDialog.getOpenFileName(self, _('Please choose a container file'), os.getenv("HOME")) if isinstance(file_path, tuple): # qt5 returns tuple path/selected filter file_path = file_path[0] self.unlock_container_file.setText(file_path) self.buttons.button(QDialogButtonBox.Ok).setText(_('Unlock')) def on_select_mountpoint_clicked(self): """ Triggered by clicking the select button next to mount point """ self.unlock_mountpoint.setText( QFileDialog.getExistingDirectory(self, _('Please choose a folder as mountpoint'), os.getenv("HOME")) ) self.buttons.button(QDialogButtonBox.Ok).setText(_('Unlock')) def on_select_keyfile_clicked(self, tab): """ Triggered by clicking the select button next to key file (both unlock and create tab) """ file_path = QFileDialog.getOpenFileName(self, _('Please choose a key file'), os.getenv("HOME")) if isinstance(file_path, tuple): # qt5 returns tuple path/selected filter file_path = file_path[0] if tab == 'Unlock': self.unlock_keyfile.setText(file_path) elif tab == 'Create': self.create_keyfile.setText(file_path) self.buttons.button(QDialogButtonBox.Ok).setText(_(tab)) def on_save_container_clicked(self): """ Triggered by clicking the select button next to container file (create) Uses a file dialog to set the path of the container file to be created """ self.create_container_file.setText(self.on_save_file(_('new_container.bin'))) def on_save_file(self, default_filename): """ Opens a native file dialog and returns the chosen path of the file to be saved The dialog does not allow overwriting existing files - to get this behaviour while using native dialogs the QFileDialog has to be reopened on overwrite. A bit weird but enables visual consistency with the other file choose dialogs :param default_filename: The default filename to be used in the Qt file dialog :type default_filename: str :returns: The designated key file path :rtype: str """ def_path = os.path.join(os.getenv("HOME"), default_filename) while True: save_path = QFileDialog.getSaveFileName(self, _('Please create a new file'), def_path, options=QFileDialog.DontConfirmOverwrite) save_path = save_path[0].strip() if isinstance(save_path, tuple) else save_path.strip() self.buttons.button(QDialogButtonBox.Ok).setText(_('Create')) # qt keeps changing this.. if os.path.exists(save_path): show_alert(self, _('File already exists:\n{filename}\n\n' 'Please create a new file!').format(filename=save_path)) def_path = os.path.join(os.path.basename(save_path), default_filename) else: return save_path def display_progress_percent(self, location, size): """ Update value on the container creation progress bar :param location: The path of the container file currently being created :type location: str :param size: The final size the new container in bytes :type size: int """ try: new_value = int(os.path.getsize(location) / size * 100) except (ZeroDivisionError, OSError, TypeError): new_value = 0 self.create_progressbars[0].setValue(new_value) def get_encrypted_container(self): """ Getter for QLineEdit text :returns: The container file path :rtype: str """ return self.unlock_container_file.text().strip() def get_luks_device_name(self): """ Getter for QLineEdit text :returns: The device name :rtype: str """ return self.unlock_device_name.text().strip() def get_keyfile(self): """ Getter for QLineEdit text :returns: The mount point path :rtype: str or None """ kf = self.unlock_keyfile.text().strip() return kf if kf != '' else None def get_mount_point(self): """ Getter for QLineEdit text :returns: The mount point path :rtype: str or None """ mp = self.unlock_mountpoint.text().strip() return mp if mp != '' else None def show_help_create(self): """ Triggered by clicking the help button (create tab) """ header_text = _('Create a new encrypted container\n') basic_help = _('Enter the path of the new container file in the textbox ' 'or click the button next to the box for a graphical create file dialog.' '\n\n' 'The device name will be used to identify the unlocked container. ' 'It can be any name up to 16 unicode characters, as long as it is unique.' '\n\n' 'The size of the container can be provided in GB or MB. The container ' 'will get initialized with random data, this can take quite a while - ' '1 hour for a 10GB container on an external drive is nothing unusual.' '\n\n' 'To speed up container creation Quickformat can be enabled to use `fallocate` ' 'instead of initializing the container with random data - this means previous data ' 'will not be overwritten and some conclusions about encrypted data inside closed ' 'containers can be drawn.\n') advanced_topics = [ {'head': _('key file'), 'text': _('A key file can be used to allow access to an encrypted container instead of a password. ' 'Using a key file resembles unlocking a door with a key in the real world - anyone with ' 'access to the key file can open your encrypted container. Make sure to store it at a ' 'protected location. Its okay to store it on your computer if you are using an already ' 'encrypted harddrive or a digital keystore. Having the key file on a ' 'small USB drive ' 'attached to your real chain of keys would be an option as well.\n' 'Since you dont have to enter a password, using a key file can be a convenient way to ' 'access your encrypted container. Just make sure you dont lose the key (file) ;)') + _('\n\n' 'Although basically any file could be used as a key file, a file with predictable content ' 'leads to similar problems as using weak passwords. Audio files or pictures are a good choice. ' 'If unsure use the `create key file` button to generate a small key file ' 'filled with random data.')}, {'head': _('encryption format'), 'text': _('The standard disk encryption format on Linux is called LUKS. ' 'With LibreCrypt ' 'you can use LUKS containers on Windows as well. ' 'The TrueCrypt format is quite popular on Windows/Mac, and can be created ' 'on Linux if `tcplay` is installed. Please note, that "hidden" TrueCrypt ' 'partitions are not supported by luckyLUKS!')}, {'head': _('filesystem'), 'text': _('Choose the ntfs filesystem to be able to access your data from Linux, ' 'Windows and Mac OSX. Since access permissions cannot be mapped from ' 'ntfs to Linux, access to ntfs devices is usually not restricted ' '-> take care when using unlocked ntfs devices in a multiuser environment!')} ] hd = HelpDialog(self, header_text, basic_help, advanced_topics) hd.exec_() def show_help_unlock(self): """ Triggered by clicking the help button (unlock tab) """ header_text = _('Unlock an encrypted container\n') basic_help = _('Select the encrypted container file by clicking the button next to ' 'the textbox. Both LUKS and Truecrypt containers are supported!' '\n\n' 'The device name will be used to identify the unlocked container. ' 'It can be any name up to 16 unicode characters, as long as it is unique ' '-> you cannot give two unlocked containers the same name') advanced_topics = [ {'head': _('key file'), 'text': _('A key file can be used to allow access to an encrypted container instead of a password. ' 'Using a key file resembles unlocking a door with a key in the real world - anyone with ' 'access to the key file can open your encrypted container. Make sure to store it at a ' 'protected location. Its okay to store it on your computer if you are using an already ' 'encrypted harddrive or a digital keystore. Having the key file on a ' 'small USB drive ' 'attached to your real chain of keys would be an option as well.\n' 'Since you dont have to enter a password, using a key file can be a convenient way to ' 'access your encrypted container. Just make sure you dont lose the key (file) ;)')}, {'head': _('mount point'), 'text': _('The mount point is the folder on your computer, where you can ' 'access the files inside the container after unlocking. ' 'If automatic mounting is configured on your system (eg with udisks), ' 'explicitly setting a mountpoint is not neccessary (but still possible).')} ] hd = HelpDialog(self, header_text, basic_help, advanced_topics) hd.exec_() luckyLUKS-2.1.0/luckyLUKS/unlockUI.py000066400000000000000000000306771420343070100173000ustar00rootroot00000000000000""" This module contains 3 different graphical modal dialogs to ask a user for password/-phrase. Each is based on a common password dialog and offers a method to use the dialog in a synchronous way ie to run itself and return a result or perform an action, or throw an exception if this fails luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) This program 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. This program 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. """ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QDialog, QMessageBox, QDialogButtonBox, QStyle, \ QLabel, QVBoxLayout, QHBoxLayout, QLineEdit, QCheckBox, QLayout, QApplication from PyQt5.QtGui import QIcon class UserInputError(Exception): """ Raised if user cancels a password dialog """ class PasswordDialog(QDialog): """ Basic dialog with a textbox input field for the password/-phrase and OK/Cancel buttons """ def __init__(self, parent, message, title='Enter Password'): """ :param parent: The parent window/dialog used to enable modal behaviour :type parent: :class:`PyQt5.QtGui.QWidget` :param message: The message that gets displayed above the textbox :type message: str :param title: Displayed in the dialogs titlebar :type title: str or None """ super().__init__(parent, Qt.WindowCloseButtonHint | Qt.WindowTitleHint) self.setWindowTitle(title) self.layout = QVBoxLayout() self.layout.setSizeConstraint(QLayout.SetFixedSize) self.layout.setSpacing(10) # create icon and label self.header_box = QHBoxLayout() self.header_box.setSpacing(10) self.header_box.setAlignment(Qt.AlignLeft) self.header_text = QLabel(message) icon = QLabel() icon.setPixmap( QIcon.fromTheme( 'dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon) ).pixmap(32) ) self.header_box.addWidget(icon) self.header_box.addWidget(self.header_text) self.layout.addLayout(self.header_box) # create the text input field self.pw_box = QLineEdit() self.pw_box.setMinimumSize(0, 25) # password will not be shown on screen self.pw_box.setEchoMode(QLineEdit.Password) self.layout.addWidget(self.pw_box) self.layout.addSpacing(10) # OK and Cancel buttons self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, parent=self) self.buttons.accepted.connect(self.on_accepted) self.buttons.rejected.connect(self.reject) self.layout.addWidget(self.buttons) self.setLayout(self.layout) self.pw_box.setFocus() def on_accepted(self): """ Event handler for accept signal: block when input field empty """ if self.pw_box.text() != '': self.accept() else: self.pw_box.setFocus() def get_password(self): """ Dialog runs itself and returns the password/-phrase entered or throws an exception if the user cancelled/closed the dialog :returns: The entered password/-phrase :rtype: str :raises: UserInputError """ try: if self.exec_(): return str(self.pw_box.text()) raise UserInputError() finally: self.destroy() class SudoDialog(PasswordDialog): """ Modified PasswordDialog that adds a checkbox asking for permanent sudo access permission""" def __init__(self, parent, message, toggle_function): """ :param parent: The parent window/dialog used to enable modal behaviour :type parent: :class:`PyQt5.QtGui.QWidget` :param message: The message that gets displayed above the textbox :type message: str :param toggle_function: A function that accepts a boolean value, propagates current state of the checkbox :type toggle_function: function(boolean) """ super().__init__(parent, message=message, title=_('luckyLUKS')) # add checkbox to dialog self.toggle_function = toggle_function self.store_cb = QCheckBox('') # QCheckBox supports only string labels .. # using QLabel because markup is needed for linebreak cb_label = QLabel(_('Always allow luckyLUKS to be run\nwith administrative privileges')) # connect clicked on QLabel to fully emulate QCheckbox behaviour cb_label.mouseReleaseEvent = self.toggle_cb self.store_cb.stateChanged.connect(self.on_cb_toggled) self.toggle_function(False) # allowing permanent access has to be confirmed explicitly self.sudo_box = QHBoxLayout() self.sudo_box.setSpacing(5) self.sudo_box.addWidget(self.store_cb) self.sudo_box.addWidget(cb_label) self.sudo_box.addStretch() self.layout.insertLayout(2, self.sudo_box) def toggle_cb(self, event): """ Slot for QCheckbox behaviour emulation: toggles checkbox """ self.store_cb.setChecked(not self.store_cb.isChecked()) def on_cb_toggled(self, state): """ Event handler for checkbox toggle: propagate new value to parent """ self.toggle_function(self.store_cb.isChecked()) class FormatContainerDialog(PasswordDialog): """ Modified PasswordDialog that shows the input on screen and requests confirmation for close/cancel """ def __init__(self, parent): """ :param parent: The parent window/dialog used to enable modal behaviour :type parent: :class:`PyQt5.QtGui.QWidget` """ super().__init__(parent, message=_('Please choose a passphrase\nto encrypt the new container:\n'), title=_('Enter new Passphrase')) # display passphrase checkbox and set default to show input self.show_cb = QCheckBox(_('Display passphrase')) self.show_cb.stateChanged.connect(self.on_cb_toggled) self.show_box = QHBoxLayout() self.show_box.addWidget(self.show_cb) self.layout.insertLayout(2, self.show_box) self.show_cb.setChecked(True) def on_cb_toggled(self): """ Event handler for checkbox toggle: show/hide passphrase input on screen """ if self.show_cb.isChecked(): # show input on screen self.pw_box.setEchoMode(QLineEdit.Normal) else: # hide input on screen self.pw_box.setEchoMode(QLineEdit.Password) def closeEvent(self, event): """ Event handler confirm close """ if not self.confirm_close_cancel(): event.ignore() def reject(self): """ Event handler confirm cancel """ if self.confirm_close_cancel(): super().reject() else: self.pw_box.setFocus() def confirm_close_cancel(self): """ Display dialog for close/cancel confirmation :returns: The users decision :rtype: bool """ message = _('Currently creating new container!\nDo you really want to quit?') mb = QMessageBox(QMessageBox.Question, '', message, QMessageBox.Ok | QMessageBox.Cancel, self) mb.button(QMessageBox.Ok).setText(_('Quit')) return mb.exec_() == QMessageBox.Ok class UnlockContainerDialog(PasswordDialog): """ Modified PasswordDialog that communicates with the worker process to unlock an encrypted container """ def __init__(self, parent, worker, luks_device_name, encrypted_container, key_file=None, mount_point=None): """ :param parent: The parent window/dialog used to enable modal behaviour :type parent: :class:`PyQt5.QtGui.QWidget` :param worker: Communication handler with the worker process :type worker: :class:`helper.WorkerMonitor` :param luks_device_name: The device mapper name :type luks_device_name: str :param encrypted_container: The path of the container file :type encrypted_container: str :param key_file: The path of an optional key file :type key_file: str or None :param mount_point: The path of an optional mount point :type mount_point: str or None """ super().__init__(parent, _('Initializing ..'), luks_device_name) self.worker = worker self.error_message = '' if key_file is not None: self.header_text.setText( _('Using keyfile\n{keyfile}\nto open container.\n\nPlease wait ..') .format(keyfile=key_file) ) self.pw_box.hide() self.buttons.hide() self.header_text.setContentsMargins(10, 10, 10, 10) else: self.buttons.button(QDialogButtonBox.Ok).setText(_('Unlock')) # disable input until worker initialized self.pw_box.setEnabled(False) self.buttons.setEnabled(False) self.waiting_for_response = True # call worker self.worker.execute(command={'type': 'request', 'msg': 'unlock', 'device_name': luks_device_name, 'container_path': encrypted_container, 'mount_point': mount_point, 'key_file': key_file }, success_callback=self.on_worker_reply, error_callback=self.on_error) def on_accepted(self): """ Event handler send password/-phrase if worker ready """ # dont send empty password if self.pw_box.text() == '': self.pw_box.setFocus() elif not self.waiting_for_response: # worker is ready, send request self.waiting_for_response = True self.pw_box.setEnabled(False) self.buttons.setEnabled(False) self.header_text.setText(_('Checking passphrase ..')) self.worker.execute(command={'type': 'response', 'msg': str(self.pw_box.text()) }, success_callback=self.on_worker_reply, error_callback=self.on_error) def reject(self): """ Event handler cancel: Block while waiting for response or notify worker with abort message """ if not self.waiting_for_response: self.worker.execute({'type': 'abort', 'msg': ''}, None, None) super().reject() def on_error(self, error_message): """ Error-Callback: set errormessage and trigger Cancel """ self.error_message = error_message super().reject() def on_worker_reply(self, message): """ Success-Callback: trigger OK when unlocked, reset dialog if not """ if message == 'success': self.accept() else: if self.pw_box.text() == '': # init self.header_text.setText(_('Please enter\ncontainer passphrase:')) else: # at least one previous pw attempt self.header_text.setText(_('Wrong passphrase, please retry!\nEnter container passphrase:')) self.buttons.setEnabled(True) self.pw_box.setText('') self.pw_box.setEnabled(True) self.pw_box.setFocus() self.waiting_for_response = False def closeEvent(self, event): """ Event handler close: block while waiting for response or notify worker with abort message """ if self.waiting_for_response: event.ignore() else: self.worker.execute({'type': 'abort', 'msg': ''}, None, None) def communicate(self): """ Dialog runs itself and throws an exception if the container wasn't unlocked :raises: UserInputError """ try: if not self.exec_(): raise UserInputError(self.error_message) # empty string if cancel/delete -> won't get displayed finally: self.destroy() luckyLUKS-2.1.0/luckyLUKS/utils.py000066400000000000000000000406031420343070100166750ustar00rootroot00000000000000""" Helper to establish a root-worker process and ipc handler. luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) This program 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. This program 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. """ import os import select import fcntl import subprocess import sys import json import traceback from PyQt5.QtCore import QThread, QEvent from PyQt5.QtWidgets import QApplication from luckyLUKS.unlockUI import PasswordDialog, SudoDialog, UserInputError from luckyLUKS.utilsUI import show_alert, show_info class SudoException(Exception): """ Errors while establishing a worker process with elevated privileges using sudo""" class WorkerMonitor(QThread): """ Establishes an asynchronous communication channel with the worker process: Since the worker executes only one task at a time queueing/sophisticated ipc are not needed. After execute is called with command and callbacks, further commands will get blocked until an answer from the worker arrives. The answer will get injected into the UI-loop -> the UI stays responsive, and thus has to disable buttons etc to prevent the user from sending additional commands """ def __init__(self, parent): """ :param parent: The parent widget to be passed to modal dialogs :type parent: :class:`PyQt5.QtGui.QWidget` :raises: SudoException """ super().__init__() self.parent = parent self.success_callback, self.error_callback = None, None self.modify_sudoers = False self.worker = None self._spawn_worker() if self.modify_sudoers: # adding user/program to /etc/sudoers.d/ requested self.execute({'type': 'request', 'msg': 'authorize'}, None, None) response = json.loads(self.worker.stdout.readline().strip()) # blocks if response['type'] == 'error': show_alert(self.parent, response['msg']) else: message = _('Permanent `sudo` authorization for\n' '{program}\n' 'has been successfully added for user `{username}` to \n' '/etc/sudoers.d/lucky-luks\n').format( program=os.path.abspath(sys.argv[0]), username=os.getenv("USER")) show_info(self.parent, message, _('Success')) def _spawn_worker(self): """ Init worker subprocess with sudo && setup ipc handler :raises: SudoException """ # using poll to wait for feedback from sudo self.pipe_events = select.poll() self._connect_to_sudo() dlg_message = _('luckyLUKS needs administrative privileges.\nPlease enter your password:') incorrent_pw_entered = False try: while True: __, event = self.pipe_events.poll()[0] # blocking # sudo process wrote to pipe -> read message msg = self.worker.stdout.read() if event & select.POLLIN: if 'ESTABLISHED' in msg: # Helper process initialized, from here on all com-messages on the pipe # will be terminated with newline -> switch back to blocking IO fl = fcntl.fcntl(self.worker.stdout.fileno(), fcntl.F_GETFL) fcntl.fcntl(self.worker.stdout.fileno(), fcntl.F_SETFL, fl & (~os.O_NONBLOCK)) break if 'SUDO_PASSWD_PROMPT' in msg: if incorrent_pw_entered: dlg_msg = _('Sorry, incorrect password.\n') + dlg_message else: dlg_msg = dlg_message self.worker.stdin.write( SudoDialog(parent=self.parent, message=dlg_msg, toggle_function=lambda val: setattr(self, 'modify_sudoers', val) ).get_password() + '\n') self.worker.stdin.flush() incorrent_pw_entered = True elif 'incorrect password attempts' in msg: # max password attempts reached -> restart sudo process and continue self._connect_to_sudo() elif 'not allowed to execute' in msg or 'not in the sudoers file' in msg: dlg_su_message = _('You are not allowed to execute this script with `sudo`.\n' 'If you want to modify your `sudo` configuration,\n' 'please enter the root/administrator password.\n') incorrent_pw_entered = False while True: master, slave = os.openpty() # su has to be run from a terminal p = subprocess.Popen( "su -c '" + sys.argv[0] + " --ishelperprocess --sudouser " + str(os.getuid()) + "'", shell=True, stdin=slave, stderr=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True, close_fds=True ) if incorrent_pw_entered: dlg_msg = _('Sorry, incorrect password.\n') + dlg_su_message else: dlg_msg = dlg_su_message os.write(master, (PasswordDialog(parent=self.parent, message=dlg_msg ).get_password() + '\n').encode('UTF-8')) p.wait() if p.returncode == 0: show_info( self.parent, _('`sudo` configuration successfully modified, now\n' 'you can use luckyLUKS with your user password.\n\n' 'If you want to grant permanent administrative rights\n' 'just tick the checkbox in the following dialog.\n'), _('Success') ) incorrent_pw_entered = False self._connect_to_sudo() break if p.returncode == 1: incorrent_pw_entered = True else: # worker prints exceptions to stdout # to keep them separated from 'su: Authentication failure' raise SudoException(p.stdout.read()) elif event & select.POLLERR or event & select.POLLHUP: raise SudoException(msg) except SudoException: # don't touch raise except UserInputError as e: # user cancelled dlg -> quit without msg raise SudoException() from e except Exception as e: # catch ANY other exception to show via gui raise SudoException( _('Communication with sudo process failed\n{error}') .format(error=''.join(traceback.format_exception(*sys.exc_info()))) ) from e finally: try: self.pipe_events.unregister(self.worker.stdout.fileno()) except KeyError: pass # fd might already gone (IOError etc) del self.pipe_events def _connect_to_sudo(self): """ Calls worker process with sudo and initializes pipes for communication """ if self.worker is not None: # disconnect event listener and wait for process termination self.pipe_events.unregister(self.worker.stdout.fileno()) self.worker.wait() # since output from sudo gets parsed, it needs to be run without localization # saving original language settings / LC-environment to pass to the worker process original_language = os.getenv("LANGUAGE", "") env_lang_cleared = {prop: os.environ[prop] for prop in os.environ if prop[0:3] == 'LC_' or prop == 'LANG'} env_lang_cleared['LANGUAGE'] = 'C' cmd = ['sudo', '-S', '-p', 'SUDO_PASSWD_PROMPT', 'LANGUAGE=' + original_language, os.path.abspath(sys.argv[0]), '--ishelperprocess'] self.worker = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, universal_newlines=True, env=env_lang_cleared) # switch pipe to non-blocking IO fd = self.worker.stdout.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) # connect event listener self.pipe_events.register(self.worker.stdout.fileno(), select.POLLIN) def run(self): """ Listens on workers stdout and executes callbacks when answers arrive """ while True: try: buf = self.worker.stdout.readline() # blocks if buf: # check if worker output pipe closed response = json.loads(buf.strip()) else: return assert('type' in response and 'msg' in response) # there should be somebody waiting for an answer! assert(self.success_callback is not None and self.error_callback is not None) # valid response received if response['type'] == 'error': QApplication.postEvent(self.parent, WorkerEvent(self.error_callback, response['msg'])) else: QApplication.postEvent(self.parent, WorkerEvent(self.success_callback, response['msg'])) # reset callbacks self.success_callback, self.error_callback = None, None except ValueError: # worker didn't return json -> probably crashed, show everything printed to stdout os.set_blocking(self.worker.stdout.fileno(), False) buf += str(self.worker.stdout.readlines()) QApplication.postEvent( self.parent, WorkerEvent(callback=lambda msg: show_alert(self.parent, msg, critical=True), response=_('Error in communication:\n{error}').format(error=_(buf))) ) return except (IOError, AssertionError) as communication_error: QApplication.postEvent( self.parent, WorkerEvent(callback=lambda msg: show_alert(self.parent, msg, critical=True), response=_('Error in communication:\n{error}').format(error=str(communication_error))) ) return def execute(self, command, success_callback, error_callback): """ Writes command to workers stdin and sets callbacks for listener thread :param command: The function to be done by the worker is in command[`msg`] the arguments are passed as named properties command[`device_name`] etc. :type command: dict :param success_callback: The function to be called if the worker finished successfully :type success_callback: function :param error_callback: The function to be called if the worker returns an error :type error_callback: function """ try: # valid command obj? assert('type' in command and 'msg' in command) # channel clear? (no qeue neccessary for the backend process) assert(self.success_callback is None and self.error_callback is None) self.success_callback = success_callback self.error_callback = error_callback self.worker.stdin.write(json.dumps(command) + '\n') self.worker.stdin.flush() except (IOError, AssertionError) as communication_error: QApplication.postEvent( self.parent, WorkerEvent(callback=lambda msg: show_alert(self.parent, msg, critical=True), response=_('Error in communication:\n{error}').format(error=str(communication_error))) ) class WorkerEvent(QEvent): """ thread-safe callback execution by raising these custom events in the main ui loop """ EVENT_TYPE = QEvent.Type(QEvent.registerEventType()) def __init__(self, callback, response): """ A WorkerEvent encapsulates a function to be called in the main ui loop and its argument :param callback: The function to be called when the event gets processed :type callback: function :param response: Response message from the worker, passed as argument to the callback function :type response: str """ QEvent.__init__(self, WorkerEvent.EVENT_TYPE) self.callback = callback self.response = response class KeyfileCreator(QThread): """ Create a 1KByte key file with random data Worker thread to avoid blocking the ui loop """ def __init__(self, parent, path): """ :param parent: The parent widget to be passed to modal dialogs :type parent: :class:`PyQt5.QtGui.QWidget` :param path: The designated key file path :type path: str """ super().__init__() self.parent = parent self.path = path self.process = None def run(self): """ Spawns child process and passes a WorkerEvent to the main event loop when finished """ try: output_file = str(self.path) except UnicodeEncodeError: output_file = self.path.encode('utf-8') # assume uft8 encoding for shell - see worker # oflag=excl -> fail if the output file already exists cmd = ['dd', 'if=/dev/random', 'of=' + output_file, 'bs=1', 'count=1024', 'conv=excl'] with open(os.devnull) as DEVNULL: self.process = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=DEVNULL, universal_newlines=True, close_fds=True) __, errors = self.process.communicate() if self.process.returncode != 0: QApplication.postEvent( self.parent.parent(), WorkerEvent(callback=lambda msg: self.parent.display_create_failed(msg, stop_timer=True), response=_('Error while creating key file:\n{error}').format(error=errors)) ) else: QApplication.postEvent( self.parent.parent(), WorkerEvent(callback=lambda msg: self.parent.on_keyfile_created(msg), response=self.path) ) def terminate(self): """ kill dd process """ self.process.kill() def is_installed(executable): """ Checks if executable is present Because the executables will be run by the privileged worker process, the usual root path gets added to the users environment path. Note: an executable at a custom user path will only be used by the worker process, if it is also present in the root path -> therefore this check might not be 100% accurate, but almost always sufficient. Checking the real root path would require calling the worker process, this way in rare cases the worker might throw an error on startup :param executable: executable to search for :type executable: str :returns: True if executable found :rtype: bool """ return any([os.path.exists(os.path.join(p, executable)) for p in os.environ["PATH"].split(os.pathsep) + ['/sbin', '/usr/sbin'] ]) luckyLUKS-2.1.0/luckyLUKS/utilsUI.py000066400000000000000000000236731420343070100171430ustar00rootroot00000000000000""" UI helper/classes for luckyLUKS luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) QExpander Copyright (c) 2012 Canonical Ltd. modified, originally from https://launchpad.net/ubuntu-sso-client (GPL v3+) This program 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. This program 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. """ from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QApplication, QMessageBox, QDialog, QVBoxLayout, QHBoxLayout,\ QWidget, QDialogButtonBox, QLabel, QStyle, QStyleOption, QSizePolicy, QFrame from PyQt5.QtGui import QPainter from luckyLUKS import VERSION_STRING, PROJECT_URL class HelpDialog(QDialog): """ Displays a help dialog that consists of a help icon and a header/title a main text an iniatially hidden secondary help text that can be expanded followed by a footer """ def __init__(self, parent, header_text, basic_text, advanced_topics): """ Create a new instance :param parent: The parent window/dialog used to enable modal behaviour :type parent: :class:`PyQt5.QtGui.QWidget` :param header_text: Displayed in the top of the dialog next to the help icon :type header_text: str :param basic_text: Displayed in the middle of the help dialog :type basic_text: str :param advanced_topics: Displayed below the basic text, initially only header is shown and content hidden :type advanced_topics: Array of dicts with str head and text properties """ super().__init__(parent, Qt.WindowCloseButtonHint | Qt.WindowTitleHint) self.setWindowTitle(_('Help')) layout = QVBoxLayout() layout.setContentsMargins(15, 5, 15, 5) layout.setSpacing(5) # icon and header header = QHBoxLayout() header.setSpacing(80) header.setAlignment(Qt.AlignLeft) icon = QLabel() icon.setPixmap(QApplication.style().standardIcon(QStyle.SP_DialogHelpButton).pixmap(48)) header.addWidget(icon) header.addWidget(QLabel(header_text)) layout.addLayout(header) # main help text basic_text = QLabel(basic_text) basic_text.setWordWrap(True) basic_text.setAlignment(Qt.AlignJustify) basic_text.setFixedWidth(470) # Qt produces unreliable layout when using wordwrap and non-fixed width basic_text.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) layout.addWidget(basic_text) # advanced help advanced = QLabel('' + _('Advanced Topics:') + '') layout.addWidget(advanced) self._advanced_topics = [] for topic in advanced_topics: head = QExpander(topic['head'], self, False) layout.addWidget(head) text = QLabel(topic['text']) text.setWordWrap(True) text.setAlignment(Qt.AlignJustify) text.setFixedWidth(470) text.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) layout.addWidget(text) head.addWidgets([text]) self._advanced_topics += [head] head.clicked.connect(self._on_topic_clicked) # footer layout.addStretch() hl = QFrame() hl.setFrameShape(QFrame.HLine) hl.setFrameShadow(QFrame.Sunken) layout.addWidget(hl) footer = QLabel(_('luckyLUKS version {version}\n' 'For more information, visit\n' '{project_url}').format(version=VERSION_STRING, project_url=PROJECT_URL)) footer.setContentsMargins(0, 10, 0, 10) layout.addWidget(footer) # button ok_button = QDialogButtonBox(QDialogButtonBox.Ok, parent=self) ok_button.accepted.connect(self.accept) layout.addWidget(ok_button) self.setLayout(layout) def _on_topic_clicked(self, clicked_topic): """An expandable topic was clicked. Closes previously opened topic if necessary""" if clicked_topic.is_expanded: for topic in self._advanced_topics: if not topic == clicked_topic: topic.setExpanded(False) def show_info(parent, message, title=''): """ Helper to show info message :param parent: The parent widget to be passed to the modal dialog :type parent: :class:`PyQt5.QtGui.QWidget` :param message: The message that gets displayed in a modal dialog :type message: str :param title: Displayed in the dialogs titlebar :type title: str """ show_message(parent, message, title, QMessageBox.Information) def show_alert(parent, message, critical=False): """ Helper to show error message :param parent: The parent widget to be passed to the modal dialog :type parent: :class:`PyQt5.QtGui.QWidget` :param message: The message that gets displayed in a modal dialog :type message: str :param critical: If critical, quit application (default=False) :type critical: bool """ show_message(parent, message, _('Error'), QMessageBox.Critical if critical else QMessageBox.Warning) if critical: QApplication.instance().quit() def show_message(parent, message, title, message_type): """ Generic helper to show message :param parent: The parent widget to be passed to the modal dialog :type parent: :class:`PyQt5.QtGui.QWidget` :param message: The message that gets displayed in a modal dialog :type message: str :param title: Displayed in the dialogs titlebar :type title: str :param message_type: Type of message box to be used :type message_type: :class:`QMessageBox.Icon` """ if message != '': mb = QMessageBox(message_type, title, message, QMessageBox.Ok, parent) # make QMessageBox better adaptable to long messages (eg stacktraces) mb.findChildren(QLabel)[1].setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) mb.exec_() class QExpander(QWidget): """A Qt implementation similar to GtkExpander.""" clicked = pyqtSignal(QWidget) def __init__(self, label, parent, expanded=False): """Create a new instance.""" super().__init__(parent) self.parent = parent self.label = QExpanderLabel(label, self) self.layout = QVBoxLayout() self.setLayout(self.layout) self.layout.addWidget(self.label) self._widgets = [] self._expanded = False self.label.clicked.connect(self._on_label_clicked) self.setExpanded(expanded) def _on_label_clicked(self): """The expander widget was clicked.""" self._expanded = not self._expanded self.setExpanded(self._expanded) self.clicked.emit(self) def addWidgets(self, widgets): """Add widgets to the expander. """ self._widgets += widgets self.setExpanded(self._expanded) def is_expanded(self): """Return if widget is expanded.""" return self._expanded def setExpanded(self, is_expanded): """Expand the widget or not.""" self._expanded = is_expanded if self._expanded: self.label.arrow.direction = QArrow.DOWN else: self.label.arrow.direction = QArrow.RIGHT for widget in self._widgets: widget.setVisible(self._expanded) self.parent.adjustSize() class QExpanderLabel(QWidget): """Widget used to show/modify the label of a QExpander.""" clicked = pyqtSignal() def __init__(self, label, parent): """Create a new instance.""" super().__init__(parent) self.arrow = QArrow(QArrow.RIGHT) self.label = QLabel(label) layout = QHBoxLayout() self.setLayout(layout) layout.addWidget(self.arrow) layout.addWidget(self.label) def mousePressEvent(self, event): """Mouse clicked.""" if self.arrow.direction == QArrow.DOWN: self.arrow.direction = QArrow.RIGHT else: self.arrow.direction = QArrow.DOWN self.clicked.emit() class QArrow(QWidget): """Custom widget, arrow image that can be pointed in 4 different directions""" UP = 0 DOWN = 1 LEFT = 2 RIGHT = 3 def __init__(self, direction, parent=None): """Create a new instance.""" super().__init__(parent) self._set_direction(direction) self.setFixedWidth(10) def paintEvent(self, event): """Paint the widget.""" opt = QStyleOption() opt.initFrom(self) painter = QPainter(self) if self._direction == QArrow.UP: primitive = QStyle.PE_IndicatorArrowUp elif self._direction == QArrow.DOWN: primitive = QStyle.PE_IndicatorArrowDown elif self._direction == QArrow.LEFT: primitive = QStyle.PE_IndicatorArrowLeft else: primitive = QStyle.PE_IndicatorArrowRight painter.setViewTransformEnabled(True) self.style().drawPrimitive(primitive, opt, painter, self) def _get_direction(self): """Return the direction used.""" return self._direction def _set_direction(self, direction): """Set the direction.""" if direction not in (QArrow.UP, QArrow.DOWN, QArrow.LEFT, QArrow.RIGHT): raise ValueError('Wrong arrow direction.') self._direction = direction self.repaint() direction = property(_get_direction, _set_direction) luckyLUKS-2.1.0/luckyLUKS/worker.py000066400000000000000000001143541420343070100170530ustar00rootroot00000000000000""" Because access to /dev/mapper is restricted to root, administrator privileges are needed to handle encrypted containers as block devices. The GUI will be run from a normal user account while all calls to cryptsetup and mount will be executed from a separate worker process with administrator privileges. Everything that runs with elevated privs is included in this module. Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) This program 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. This program 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. """ import subprocess import os import json import sys import traceback import pwd import stat import warnings import threading import signal import random from uuid import uuid4 from time import sleep import queue class WorkerException(Exception): """ Used to catch error messages from shell calls to give feedback via gtk-gui """ class UserAbort(Exception): """ Worker gets notified about user canceling the command -> no response needed """ def com_thread(cmdqueue): """ Monitors the incoming pipe and puts commands in a queue. Linux signals cannot be sent from the parent to a privileged childprocess, so to send sigterm this thread detects if the parent closes the pipe instead, to terminate the main thread and all its child processes. :param cmdqueue: Queue to pass incoming commands to main thread :type cmdqueue: queue """ while True: try: buf = sys.stdin.readline().strip() # blocks if not buf: # check if input pipe closed break cmdqueue.put(json.loads(buf)) except IOError: break # send INT to all child processes (eg currently creating new container with dd etc..) os.killpg(0, signal.SIGINT) def run(): """ Initialize helper and setup ipc. Reads json encoded, newline terminated commands from stdin, performs the requested command and returns the json encoded, newline terminated answer on stdout. All commands are executed sequentially, but stdin.readline() gets read in another thread that monitors the incoming pipe and passes to a queue or quits the helper process if the parent closes the pipe. Watching for closed pipe is needed because normal signals cannot be sent to a privileged childprocess """ if os.getuid() != 0: sys.stdout.write(_('Please call with sudo.')) sys.exit(1) elif os.getenv("SUDO_USER") is None or os.getenv("SUDO_UID") is None or os.getenv("SUDO_GID") is None: sys.stdout.write(_('Missing information of the calling user in sudo environment.\n' 'Please make sure sudo is configured correctly.')) sys.exit(1) else: # send ack to establish simple json encoded request/response protocol using \n as terminator sys.stdout.write('ESTABLISHED') sys.stdout.flush() # start thread to monitor the incomming pipe, communicate via queue cmdqueue = queue.Queue() worker = WorkerHelper(cmdqueue) t = threading.Thread(target=com_thread, args=(cmdqueue,)) t.start() # create process group to be able to quit all child processes of the worker os.setpgrp() with warnings.catch_warnings(): warnings.filterwarnings('error') # catch warnings to keep them from messing up the pipe while True: response = {'type': 'response', 'msg': 'success'} # return success unless exception try: # arbitrary timeout needed to be able to get instant KeyboardInterrupt # pythons queue.get() without timeout prefers not to be interrupted by KeyboardInterrupt :) cmd = cmdqueue.get(timeout=32767) if cmd['msg'] == 'status': is_unlocked = worker.check_status(cmd['device_name'], cmd['container_path'], cmd['key_file'], cmd['mount_point']) if not is_unlocked and cmd['key_file'] is not None: # if keyfile used try to unlock on startup worker.unlock_container(cmd['device_name'], cmd['container_path'], cmd['key_file'], cmd['mount_point']) response['msg'] = 'unlocked' else: response['msg'] = 'unlocked' if is_unlocked else 'closed' elif cmd['msg'] == 'unlock': worker.unlock_container(cmd['device_name'], cmd['container_path'], cmd['key_file'], cmd['mount_point']) elif cmd['msg'] == 'close': worker.close_container(cmd['device_name'], cmd['container_path']) elif cmd['msg'] == 'create': worker.create_container(cmd['device_name'], cmd['container_path'], cmd['container_size'], cmd['filesystem_type'], cmd['encryption_format'], cmd['key_file'], cmd['quickformat']) elif cmd['msg'] == 'authorize': worker.modify_sudoers(os.getenv("SUDO_UID"), nopassword=True) else: raise WorkerException(_('Helper process received unknown command')) except queue.Empty: continue # timeout reached on queue.get() -> keep polling except UserAbort: continue # no response needed except WorkerException as we: response = {'type': 'error', 'msg': str(we)} except KeyError as ke: # thrown if required parameters missing response = {'type': 'error', 'msg': _('Error in communication:\n{error}').format(error=str(ke))} except KeyboardInterrupt: # gets raised by killpg -> quit sys.exit(0) except Exception: # catch ANY exception (including warnings) to show via gui response = {'type': 'error', 'msg': ''.join(traceback.format_exception(*sys.exc_info()))} sys.stdout.write(json.dumps(response) + '\n') sys.stdout.flush() class WorkerHelper(): """ accepts 5 commands: -> check_status() validates the input and returns the current state (unlocked/closed) of the container -> unlock_container() asks for the passphrase and tries to unlock and mount a container -> close_container() closes and unmounts a container -> create_container() initializes a new encrypted LUKS container and sets up the filesystem -> modify_sudoers() adds sudo access to the program without password for the current user (/etc/sudoers.d/) """ def __init__(self, cmdqueue=None): """ Check tcplay installation """ self.cmdqueue = cmdqueue self.is_tc_installed = any([os.path.exists(os.path.join(p, 'tcplay')) for p in os.environ["PATH"].split(os.pathsep)]) def communicate(self, request): """ Helper to get an synchronous response from UI (obtain Passphrase or signal create progress) :param request: message to send to the UI :type request: str :returns: The JSON encoded response from the UI :rtype: str :raises: UserAbort """ sys.stdout.write(json.dumps({'type': 'request', 'msg': request}) + '\n') sys.stdout.flush() response = self.cmdqueue.get() # wait for response try: assert('type' in response and 'msg' in response) except AssertionError as ae: raise UserAbort() from ae # quit if abort or unexpected msg from ui if response['type'] != 'response': raise UserAbort() return response['msg'] def check_status(self, device_name, container_path, key_file=None, mount_point=None): """ Validates the input and returns the current state (unlocked/closed) of the container. The checks are sufficient to keep users from shooting themselves in the foot and provide the most possible protection against TOCTOU attacks: Since most commands are executed using the device mapper name, there is not much attack surface for TOCTOU anyway, since root access is needed to access device mapper. It would be possible to unmount other users containers with the right timing though - slightly annoying but no serious threat :param device_name: The device mapper name :type device_name: str :param container_path: The path of the container file :type container_path: str :param key_file: The path to an optional keyfile to be used for the container :type key_file: str or None :param mount_point: The path of an optional mount point :type mount_point: str or None :returns: True if LUKS device is active/unlocked :rtype: bool :raises: WorkerException """ uid = int(os.getenv("SUDO_UID")) # device_name and container_path valid? if device_name == '': raise WorkerException(_('Device Name is empty')) # check access rights to container file if not os.path.exists(container_path) or os.stat(container_path).st_uid != uid: sleep(random.random()) # 0-1s to prevent misuse of exists() raise WorkerException( _('Container file not accessible\nor path does not exist:\n\n{file_path}') .format(file_path=container_path) ) is_unlocked = self.is_LUKS_active(device_name) if is_unlocked: # make sure container file currently in use for device name is the same as the supplied container path if container_path != self.get_container(device_name): raise WorkerException(_('Could not use container:\n{file_path}\n' '{device_name} is already unlocked\n' 'using a different container\n' 'Please change the name to unlock this container') .format(file_path=container_path, device_name=device_name)) # container is not unlocked else: # validate device name if len(bytes(device_name, 'utf-8')) > 16: # the goal here is not to confuse the user: # thus Name==Label of the partition inside the encrypted container # -> because thats what usually gets presented to the user (filemanager) # see checks in create_container below for length restictions on partition labels # dev/mapper would only be able to handle slightly longer names for unicode anyways raise WorkerException( _('Device Name too long:\n' 'Only up to 16 characters possible, even less for unicode\n' '(roughly 8 non-ascii characters possible)') ) if device_name[0:1] == '-' or '/' in device_name: # cryptsetup thinks -isanoption and "/" is not supported by devmapper raise WorkerException(_('Illegal Device Name!\nNames starting with `-` or using `/` are not possible')) # prevent container from being unlocked multiple times with different names container_found = subprocess.check_output( ['losetup', '-j', container_path], stderr=subprocess.STDOUT, universal_newlines=True ) if container_found != '': # container is already in use -> try to find out the device name existing_device_name = '' encrypted_devices = subprocess.check_output( ['dmsetup', 'status', '--target', 'crypt'], stderr=subprocess.STDOUT, universal_newlines=True ).strip() for encrypted_device in encrypted_devices.split('\n'): encrypted_device = encrypted_device[:encrypted_device.find(':')].strip() if container_path == self.get_container(encrypted_device): existing_device_name = encrypted_device break raise WorkerException( _('Cannot use the container\n' '{file_path}\n' 'The container is already in use ({existing_device}).') .format(file_path=container_path, existing_device=existing_device_name) ) # validate key_file if given if (key_file is not None and any([(not os.path.exists(key_file)), (os.stat(key_file).st_uid != int(os.getenv("SUDO_UID")))])): sleep(random.random()) # 0-1s to prevent misuse of exists() raise WorkerException( _('Key file not accessible\nor path does not exist:\n\n{file_path}') .format(file_path=key_file)) # validate mount_point if given if mount_point is not None: if not os.path.exists(mount_point) or os.stat(mount_point).st_uid != uid: sleep(random.random()) # 0-1s to prevent misuse of exists() raise WorkerException( _('Mount point not accessible\nor path does not exist:\n\n{mount_dir}') .format(mount_dir=mount_point) ) if os.path.ismount(mount_point): raise WorkerException( _('Already mounted at mount point:\n\n{mount_dir}') .format(mount_dir=mount_point) ) if os.listdir(mount_point): raise WorkerException( _('Designated mount directory\n{mount_dir}\nis not empty') .format(mount_dir=mount_point) ) return is_unlocked def unlock_container(self, device_name, container_path, key_file=None, mount_point=None, pw_callback=None): """ Unlocks LUKS or Truecrypt containers. Validates input and keeps asking for the passphrase until successfull unlock, followed by an optional mount. :param device_name: The device mapper name :type device_name: str :param container_path: The path of the container file :type container_path: str :param key_file: The path to an optional keyfile to be used for the container :type key_file: str or None :param mount_point: The path of an optional mount point :type mount_point: str or None :param pw_callback: A callback function that returns the password for unlocking :type pw_callback: function() :raises: WorkerException """ is_unlocked = self.check_status(device_name, container_path, key_file, mount_point) if not is_unlocked: # just return if unlocked -> does not mount an already unlocked container if pw_callback is None: pw_callback = lambda: self.communicate('getPassword') # workaround udisks-daemon crash (udisksd from udisks2 is okay): although cryptsetup is able to handle # loopback device creation/teardown itself, using this crashes udisks-daemon # -> manual loopback device handling here # TODO: could be removed, udisks is replaced with udisks2 since ~2016 try: loop_dev = subprocess.check_output( ['losetup', '-f', '--show', container_path], stderr=subprocess.PIPE, universal_newlines=True ).strip() except subprocess.CalledProcessError as cpe: # most likely no more loopdevices available raise WorkerException(cpe.output) from cpe crypt_initialized = False try: # check if LUKS container, try Truecrypt otherwise (tc container cannot be identified by design) container_is_luks = (subprocess.call(['cryptsetup', 'isLuks', container_path]) == 0) if container_is_luks: open_command = ['cryptsetup', 'open', loop_dev, device_name] else: open_command = ['cryptsetup', 'open', '--type', 'tcrypt', loop_dev, device_name] with open(os.devnull) as DEVNULL: if key_file is None: while not is_unlocked: p = subprocess.Popen( open_command, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=DEVNULL, universal_newlines=True, close_fds=True ) __, errors = p.communicate(pw_callback()) if p.returncode == 0: is_unlocked = True elif p.returncode == 2: # cryptsetup: no permission (bad passphrase) continue else: raise WorkerException(errors) else: open_command += ['--key-file', key_file] p = subprocess.Popen( open_command, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=DEVNULL, universal_newlines=True, close_fds=True ) if container_is_luks: __, errors = p.communicate() else: # tcplay with keyfile only means empty password __, errors = p.communicate('\n') if p.returncode != 0: if p.returncode == 2: # error message from cryptsetup is a bit ambiguous raise WorkerException(_('Open container failed.\nPlease check key file')) raise WorkerException(errors) crypt_initialized = True finally: if not crypt_initialized: self.detach_loopback_device(loop_dev) if mount_point is not None: # only mount if optional parameter mountpoint is set try: subprocess.check_output( ['mount', '-o', 'nosuid,nodev', self.get_device_mapper_name(device_name), mount_point], stderr=subprocess.STDOUT, universal_newlines=True) except subprocess.CalledProcessError as cpe: raise WorkerException(cpe.output) from cpe def close_container(self, device_name, container_path): """ Validates input and tries to unmount /dev/mapper/ and close container :param device_name: The device mapper name :type device_name: str :param container_path: The path of the container file :type container_path: str :raises: WorkerException """ if self.check_status(device_name, container_path): # just return if not unlocked # for all mounting /dev/mapper/device_name is used try: # unfortunately, umount returns the same errorcode for device not mounted and device busy # clear locale to be able to parse output env_lang_cleared = os.environ.copy() env_lang_cleared['LANGUAGE'] = 'C' subprocess.check_output( ['umount', self.get_device_mapper_name(device_name)], stderr=subprocess.STDOUT, universal_newlines=True, env=env_lang_cleared ) except subprocess.CalledProcessError as cpe: if 'not mounted' not in cpe.output: # ignore if not mounted and proceed with closing the container raise WorkerException(_('Unable to close container, device is busy')) from cpe # get reference to loopback device before closing the container associated_loop = self.get_loopback_device(device_name) try: subprocess.check_output( ['cryptsetup', 'close', device_name], stderr=subprocess.STDOUT, universal_newlines=True ) except subprocess.CalledProcessError as cpe: raise WorkerException(cpe.output) from cpe # remove loopback device sleep(0.2) # give udisks some time to process closing of container .. self.detach_loopback_device(associated_loop) def create_container(self, device_name, container_path, container_size, filesystem_type, enc_format, key_file=None, quickformat=False): """ Creates a new LUKS2 container with requested size and filesystem after validating parameters Three step process: asks for passphrase after initializing container with random bits, and signals successful LUKS initialization before writing the filesystem :param device_name: The device mapper name, used as filesystem label as well :type device_name: str :param container_path: The path of the container file to be created :type container_path: str :param container_size: The size the new container in KB :type container_size: int :param filesystem_type: The type of the filesystem inside the new container (supported: 'ext4', 'ext2', 'ntfs') :type filesystem_type: str :param enc_format: The type of the encryption format used for the new container (supported: 'LUKS', 'TrueCrypt') :type enc_format: str :param key_file: The path to an optional keyfile to be used for the container :type key_file: str or None :param quickformat: Use fallocate instead of initializing container with random data :type quickformat: bool :raises: WorkerException """ # STEP0: ######################################################################### # Sanitize user input and perform some checks before starting the process to avoid # failure later on eg. dd writing out random data for 3 hours to initialize the # new container, only to find out that there is not enough space on the device, # or that the designated device name is already in use on /dev/mapper # # validate container file if os.path.basename(container_path).strip() == '': raise WorkerException(_('Container Filename not supplied')) # validate device name if device_name == '': raise WorkerException(_('Device Name is empty')) if device_name[0:1] == '-' or '/' in device_name: # cryptsetup thinks -isanoption and "/" is not supported by devmapper raise WorkerException(_('Illegal Device Name!\nNames starting with `-` or using `/` are not possible')) if len(bytes(device_name, 'utf-8')) > 16: # ext-labels are the most restricted (max 16Bytes) # dev-mapper and ntfs-label would support slightly longer strings.. raise WorkerException(_('Device Name too long:\n' 'Only up to 16 characters possible, even less for unicode \n' '(roughly 8 non-ascii characters possible)')) if os.path.exists(self.get_device_mapper_name(device_name)): raise WorkerException(_('Device Name already in use:\n\n{device_name}').format(device_name=device_name)) # validate container size if container_size < 5242880: raise WorkerException(_('Container size too small\n' 'to create encrypted filesystem\n' 'Please choose at least 5MB')) container_dir = os.path.dirname(container_path) if not os.path.dirname(container_dir): container_dir = os.path.expanduser('~' + os.getenv("SUDO_USER")) container_path = os.path.join(container_dir, os.path.basename(container_path)) free_space = os.statvfs(container_dir) free_space = free_space.f_bavail * free_space.f_bsize if container_size > free_space: raise WorkerException( _('Not enough free disc space for container:\n\n' '{space_needed} MB needed\n{space_available} MB available') .format(space_needed=str(int(container_size / 1024 / 1024)), space_available=str(int(free_space / 1024 / 1024))) ) # validate key_file if given if key_file is not None: # check access rights to keyfile if not os.path.exists(key_file) or os.stat(key_file).st_uid != int(os.getenv("SUDO_UID")): sleep(random.random()) # 0-1s to prevent misuse of exists() raise WorkerException( _('Key file not accessible\nor path does not exist:\n\n{file_path}').format(file_path=key_file) ) # validate encryption_format and filesystem # TODO: exFAT? if filesystem_type not in ['ext4', 'ext2', 'ntfs']: raise WorkerException( _('Unknown filesystem type: {filesystem_type}').format(filesystem_type=str(filesystem_type)) ) if enc_format == 'Truecrypt' and not self.is_tc_installed: raise WorkerException(_('If you want to use TrueCrypt containers\n' 'make sure `cryptsetup` is at least version 1.6 (`cryptsetup --version`)\n' 'and `tcplay` is installed (eg for Debian/Ubuntu `apt-get install tcplay`)')) # STEP1: ########################################################## # create container file by filling allocated space with random bits # # runas user to fail on access restictions if quickformat: # does not fail if the output file already exists, but check is in setupUI.on_save_file() anyway cmd = ['sudo', '-u', os.getenv("SUDO_USER"), 'fallocate', '-x', '-l', str(container_size), container_path] else: count = str(int(container_size / 1024 / 1024)) + 'K' # oflag=excl -> fail if the output file already exists cmd = ['sudo', '-u', os.getenv("SUDO_USER"), 'dd', 'if=/dev/urandom', 'of=' + container_path, 'bs=1K', 'count=' + count, 'conv=excl'] with open(os.devnull) as DEVNULL: p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=DEVNULL, universal_newlines=True, close_fds=True) __, errors = p.communicate() if p.returncode != 0: # 'sudo -u' might add this -> don't display raise WorkerException(errors.replace('Sessions still open, not unmounting', '').strip()) # TODO: this can only work with english locale, # but this errormessage doesn't seem to be localized in sudo yet .. # get rid of the problem (strip env?) or remove msg in all languages # setup loopback device with created container try: reserved_loopback_device = subprocess.check_output( ['losetup', '-f', '--show', container_path], stderr=subprocess.PIPE, universal_newlines=True ).strip() except subprocess.CalledProcessError as cpe: raise WorkerException(cpe.output) from cpe # STEP2: ###################################################### # ask user for password and initialize LUKS/TrueCrypt container # resp = '' try: if key_file is None: resp = self.communicate('getPassword') else: self.communicate('containerDone') if enc_format == 'LUKS': cmd = ['cryptsetup', 'luksFormat', '--type', 'luks2', '-q', reserved_loopback_device] if key_file is not None: cmd += ['--key-file', key_file] with open(os.devnull) as DEVNULL: p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=DEVNULL, universal_newlines=True, close_fds=True) __, errors = p.communicate(resp) if p.returncode != 0: raise WorkerException(errors) elif enc_format == 'TrueCrypt': with open(os.devnull) as DEVNULL: # secure erase already done with dd, no need to use tcplay for that cmd = ['tcplay', '-c', '-d', reserved_loopback_device, '--insecure-erase'] if key_file is not None: cmd += ['--keyfile', key_file] p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=DEVNULL, universal_newlines=True, close_fds=True) # tcplay needs the password twice & confirm -> using sleep instead of parsing output # ugly, but crypt-init takes ages with truecrypt anyways sleep(1) p.stdin.write(resp + '\n') p.stdin.flush() sleep(1) p.stdin.write(resp + '\n') p.stdin.flush() sleep(1) p.stdin.write('y\n') # .. until tcplay gets localized :) p.stdin.flush() p.stdin.close() p.stderr.close() p.wait() if p.returncode != 0: raise WorkerException('TCPLAY ERROR') else: raise WorkerException(_('Unknown encryption format: {enc_fmt}').format(enc_fmt=enc_format)) self.communicate('formatDone') # signal status finally: # cleanup loopback device self.detach_loopback_device(reserved_loopback_device) # STEP3: ############################################ # open encrypted container and format with filesystem # pw_callback = lambda: resp self.unlock_container(device_name=device_name, container_path=container_path, key_file=key_file, pw_callback=pw_callback) resp = None # get rid of pw # fs-root of created ext-filesystem should belong to the user device_mapper_name = self.get_device_mapper_name(device_name) if filesystem_type == 'ext4': cmd = ['mkfs.ext4', '-L', device_name, '-O', '^has_journal', '-m', '0', '-q', device_mapper_name] elif filesystem_type == 'ext2': cmd = ['mkfs.ext2', '-L', device_name, '-m', '0', '-q', device_mapper_name] elif filesystem_type == 'ntfs': cmd = ['mkfs.ntfs', '-L', device_name, '-Q', '-q', device_mapper_name] try: subprocess.check_output(cmd, stderr=subprocess.STDOUT, universal_newlines=True) except subprocess.CalledProcessError as cpe: raise WorkerException(cpe.output) from cpe # remove group/other read/execute rights from fs root if possible if filesystem_type != 'ntfs': tmp_mount = os.path.join('/tmp/', str(uuid4())) os.mkdir(tmp_mount) try: subprocess.check_output( ['mount', '-o', 'nosuid,nodev', device_mapper_name, tmp_mount], stderr=subprocess.STDOUT, universal_newlines=True ) except subprocess.CalledProcessError as cpe: raise WorkerException(cpe.output) from cpe os.chown(tmp_mount, int(os.getenv("SUDO_UID")), int(os.getenv("SUDO_GID"))) os.chmod(tmp_mount, 0o700) self.close_container(device_name, container_path) def modify_sudoers(self, user_id, nopassword=False): """ Adds sudo access to the program (without password) for the current user (/etc/sudoers.d/) :param user_id: unix user id :type user_id: int :returns: should access be granted without password :rtype: bool """ program_path = os.path.abspath(sys.argv[0]) try: user_name = pwd.getpwuid(int(user_id))[0] except KeyError as ke: raise WorkerException(_('Cannot change sudo rights, invalid username')) from ke if any([(os.stat(program_path).st_uid != 0), (os.stat(program_path).st_gid != 0), (os.stat(program_path).st_mode & stat.S_IWOTH)]): raise WorkerException(_('I`m afraid I can`t do that.\n\n' 'To be able to permit permanent changes to sudo rights,\n' 'please make sure the program is owned by root\n' 'and not writeable by others.\n' 'Execute the following commands in your shell:\n\n' 'chmod 755 {program}\n' 'sudo chown root:root {program}\n\n').format(program=program_path)) if not os.path.exists('/etc/sudoers.d'): os.makedirs('/etc/sudoers.d') sudoers_file_path = '/etc/sudoers.d/lucky-luks' sudoers_file = open(sudoers_file_path, 'a') sudoers_file.write( "{username} ALL = (root) {nopasswd}{program}\n".format( username=user_name, nopasswd='NOPASSWD: ' if nopassword else '', program=program_path ) ) sudoers_file.close() os.chmod(sudoers_file_path, 0o440) def is_LUKS_active(self, device_name): """ Checks if device is active/unlocked :param device_name: The device mapper name :type device_name: str :returns: True if active LUKS device found :rtype: bool """ with open(os.devnull) as DEVNULL: returncode = subprocess.call( ['cryptsetup', 'status', device_name], stdout=DEVNULL, stderr=subprocess.STDOUT ) return returncode == 0 def detach_loopback_device(self, loopback_device): """ Detaches given loopback device :param loopback_device: The loopback device path (eg /dev/loop2) :type loopback_device: str """ with open(os.devnull) as DEVNULL: subprocess.call(['losetup', '-d', loopback_device], stdout=DEVNULL, stderr=subprocess.STDOUT) def get_loopback_device(self, device_name): """ Returns the corresponding loopback device path to a given device mapper name :param device_name: The device mapper name :type device_name: str :returns: The corresponding loopback device path (eg /dev/loop2) :rtype: str """ return self.get_crypt_status(device_name, 'device:') def get_container(self, device_name): """ Returns the corresponding container path to a given device mapper name :param device_name: The device mapper name :type device_name: str :returns: The corresponding container path :rtype: str """ return self.get_crypt_status(device_name, 'loop:') def get_crypt_status(self, device_name, search_property): """ Parses cryptsetup status output for a device mapper name and returns either loopback device or container path :param device_name: The device mapper name :type device_name: str :param search_property: The property to return from status output :type search_property: 'device:' or 'loop:' :returns: loopback device or container path :rtype: str """ try: stat_output = subprocess.check_output( ['cryptsetup', 'status', device_name], stderr=subprocess.STDOUT, universal_newlines=True ) # parsing status output: should be safe, output unchanged in cryptsetup since version 1.3 (2011) for line in stat_output.split('\n'): if search_property in line: return line[line.find('/'):].strip() return '' except subprocess.CalledProcessError: return '' # device not found def get_device_mapper_name(self, device_name): """ Mapping for filesystem access to /dev/mapper/ Escapes most non alphanumeric and all unicode characters in the device name -> from the device-mapper sources: * Mangle all characters in the input string which are not on a whitelist * with '\xCC' format, where CC is the hex value of the character. * Actually, DM supports any character in a device name. * This whitelist is just for proper integration with udev. :param device_name: The device mapper name :type device_name: str :returns: The mangled device mapper name for filesystem access :rtype: str """ dm_name = '/dev/mapper/' # use single-char list for python3 (splitting 2 or more byte utf8-encoded chars..) for char in [chr(byte) for byte in bytes(device_name, 'utf-8')]: if any([(char >= '0' and char <= '9'), (char >= 'A' and char <= 'Z'), (char >= 'a' and char <= 'z'), (char in "#+-.:=@_")]): dm_name += char else: dm_name += '\\' + str(hex(ord(char))[1:]) return dm_name luckyLUKS-2.1.0/luckyluks000077500000000000000000000020471420343070100153110ustar00rootroot00000000000000#!/usr/bin/env python3 """ luckyLUKS is a GUI for creating and (un-)locking LUKS volumes from container files. For more information visit: http://github.com/jas-per/luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) This program 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. This program 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. """ import locale import gettext import os.path import inspect from luckyLUKS import main locale.setlocale(locale.LC_ALL, '') locale_dir = os.path.join(os.path.dirname(inspect.getsourcefile(main)), 'locale') translation = gettext.translation('luckyLUKS', locale_dir, fallback=True) main.luckyLUKS(translation) luckyLUKS-2.1.0/luckyluks.desktop000077500000000000000000000006531420343070100167620ustar00rootroot00000000000000[Desktop Entry] Name=luckyLUKS Name[de]=luckyLUKS Comment=GUI for creating and unlocking LUKS/TrueCrypt volumes from container files Comment[de]=GUI zum Erstellen und Öffnen von verschlüsselten Containern (LUKS/TrueCrypt) GenericName=Encrypted Container Tool GenericName[de]=Verschlüsselte Container Exec=luckyluks Categories=Utility; Icon=dialog-password NoDisplay=false StartupNotify=false Terminal=false Type=Application luckyLUKS-2.1.0/setup.cfg000066400000000000000000000007521420343070100151570ustar00rootroot00000000000000[extract_messages] add-comments = L10n: copyright-holder = Jasper van Hoorn (muzius@gmail.com) msgid-bugs-address = Jasper van Hoorn (muzius@gmail.com) [luckyLUKS] section: utils mime-desktop-files: luckyluks.desktop package: luckyluks package3: luckyluks Build-Depends: python3 (>= 3.5), dh-python depends3: sudo (>= 1.8), cryptsetup (>= 2:2.2), cryptsetup-bin (>= 2:2.2), python3-pyqt5 (>= 5.11) suggests3: tcplay copyright-file: LICENSE maintainer: Jasper van Hoorn luckyLUKS-2.1.0/setup.py000066400000000000000000000046651420343070100150570ustar00rootroot00000000000000""" config for setuptools/stdeb """ from setuptools import setup from luckyLUKS import VERSION_STRING long_desc = """luckyLUKS is a Linux GUI for creating and (un-)locking encrypted volumes from container files. Unlocked containers leave an icon in the systray as a reminder to close them eventually ;) Supports cryptsetup/LUKS and Truecrypt container files. The container is basically a large file that encapsulates an encrypted partition. This simplifies handling and backup of encrypted data for casual users. luckyLUKS follows a keep-it-simple philosophy for creating and using encrypted containers, that aims to keep users from shooting themselves in the foot. For quick access the GUI offers to add a shortcut for unlocking a specific container to the start menu or on the desktop. For more information and a complete FAQ see https://github.com/jas-per/luckyLUKS""" setup(name='luckyLUKS', version=VERSION_STRING, author='Jasper van Hoorn', author_email='muzius@gmail.com', url='https://github.com/jas-per/luckyLUKS', download_url='https://github.com/jas-per/luckyLUKS', description='GUI for encrypted LUKS or TrueCrypt containers', long_description=long_desc, platforms=['Linux'], packages=['luckyLUKS'], package_data={'luckyLUKS': ['locale/*/LC_MESSAGES/*']}, scripts=['luckyluks'], keywords='python tools utils cryptsetup LUKS TrueCrypt encryption container block device mapper GUI tcplay', license='GPL', classifiers=['Development Status :: 5 - Production/Stable', 'Environment :: X11 Applications :: Qt', 'Intended Audience :: End Users/Desktop', 'Natural Language :: English', 'Natural Language :: German', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 'Topic :: Utilities', 'Topic :: Security :: Cryptography', ], ) luckyLUKS-2.1.0/signing-key.asc000066400000000000000000000073631420343070100162570ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 mQINBFSYePwBEADj2AJIzIx9S1WNGzPRFutQC9Ue663hiWjru6HvZ6gSPm8wbgzD jEZ0LQcsgAJsl8c1Fc/pKcH03TO0yULKnCas8K4lFg81OmayzcxB5BrerbK0grmG ZD7lROyExm1Y0t8r5Un81PYeR52tSI7c0OuoE6cCUtbl7FMkXRvTJkIRbaqI6cPo tUPQWoXhqbnbHeO8HHMIQQ8Ngnst1XOrVXyprVXHk5HuLEvAJ/+96iDrWODTYUXN sfCSq7iw1URvPljozaLrtwuBShlRLZRDF2T2ydZZ0FPj2r32yZIUvynOkV8V+U9O pZKzeepW5IDYXCHRHXxLMK/ydtxuzP/LKT7NCcToCppehtkDFTg/OZQXARu5YBDn IgwFGmGeq33UVzZlJfPu4C42GiVNfgy6qV3Qd1BOUVVIZnpr52ErZtuderISgsQy JwTVJ3aVU6krak5pXYg4e8jMOwRIkOBOb11gBcs2ObFwvf6rWkaRotvKm4+14tb3 4RsEbzWyCOptpkCV+fU+REaqDPjBwqmE9s+UXPKoPVkFzALTCZPoYZ4laaM7VYGN dj0uAvuHM/TlHnj/Bs9XI2UskoDANCdqc4h0Y2DACNd4Nj70CTtw9VmDvRLh2FGL O6Par7HRc5QYLDrVUhjdKOkRwJsfpR6lSNXgxRWrxGRh4g5DgctqmNcxDwARAQAB tCNKYXNwZXIgdmFuIEhvb3JuIDxtdXppdXNAZ21haWwuY29tPokCOAQTAQIAIgUC VJh4/AIbLwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQD5DVEqt95z1CIxAA xLy9SPwFj2DsVt9/K8lqvfpQ82Fi57Kf+35JTh55zFLW+5bxioZRv2AFCQSQm9Xy aoCoguXLUHlzDi2xyCzk255JFYyAw5oznSWf6YBwjldVxFV/lqI4Wkty8n6v1TaD N3mdnwwW37v5EjrZO1dro1+M+Fqclhs6yUZ7vDs0bPu5E3AOmbCvRiTDCF4m4aol 35ul8MGCWJjFzH+LgPjVCdxST+NCqssbr+oYNTXA0pmtvtpbzX6xaRU/ToQzLukm ktuNiQ83d0606j+FnXEUGA9zT6i7nNK9Sz28hUH03kJnRpTSdKEAtZPTL7p+Cku6 PR2Djns5tx4MOes1TUnLUhTxzLMjYk2BkmSrklujpHV/DD7fPiYTlAigO5GfauXY f4yyxluIX2qCS9zx7EVfBTPwZ4aITVHzf1e+iov+KgqI5CHiYJSZ3wlQvSFKGGkt bQcvfx0USsvHSwEVWhTMLoA0mbmkIDiKXXOM+ptp9eID1cIRxNvTHlNgcQEchPs4 oKkQeckTrkYh3Fir8NarRVNyts095kujYorPrd+VbH2F8Vuvu64IPH2fPcC+ArV/ mBEeArPCWsmmoT3m/TKr7HchtgaQugC1VKZJjRoVHCtqYz4UAJLnXs7Wf8F2ao1G fAaA+xLW532OmCZbPSUo6otqwa1pTLv2mQnay5eX0eS5Ag0EVJh4/AEQAL4Flr3/ cNeJPqSgwDndyoMlQ9q1ncbawO+scMS14NK0LtawZXQ6VwgYnErl4DaztQsp11Zm 2llC6gWnIbhNyEzXF94QHMji9M2m6J9ZbSStV9IVkRefHw947HcdpLYsS4PT8pS/ k6gRtTv8x2zsIq2z8L3oOHPZQKlTEp/YTlT3JDZns6ZLDt5RXCvRF+Ag4lLqM8qR b17nFhNkk88EHhwXnTQpdOCnu8Wdc9jpvf+itA3NG+qy2Ss7GM3boZpYG8SosSWp lqtKFXSq1EOmeW1H3fW8cKqdu8iqFPGBlkzY9ymSgtjFVHUIivq3bcoQEPFoTohM 46UnXveTT3TmUedu927AJfHN0dg4r/+jRzt5vCwWk85AxgDfTrF27jBNfBTLY1ri Fp5WwmRRTXlRAgql6Hv22wHHxXBfwvO5bU8Q9qbJ8ZnjEVWY1tBMkYqNQ/pBII2u yewR8gqTrNXXBu1Yp0yx6LFlsp43OjiHGG0wqm8v+FiwUceBzFInrjUiX1vi068U ZJ32WHtejKM/sV5OTjpX1WU/A4VZNhwTaYSIgZ46b1xZo0PP1YBOUBPV8MGCubHm JV9ClqD8rAX+Zwl4rNY6x+fHCpYTGPjrfrSM5Csz88dnZK6PnGQndjwdRSjqnOce ohqG8dYN6mDqbSvDNsyHayCRwLk2jXMixz+ZABEBAAGJBD4EGAECAAkFAlSYePwC Gy4CKQkQD5DVEqt95z3BXSAEGQECAAYFAlSYePwACgkQSr/2ghc2Ns5OzhAAjDsj jC1QpPTWPaCWE/MsG2O0df8XMNmHkOjel3u1S1f9NmGT0nekznrAe2wCVdgCEBPh LjsZlsaemc0Mj4TtqfjGCnd2CoheBzjg2OxPxOXRYUozsLnfqxlePVxzjzaB8hnE xax+1tAOHYPVYU8UHIXCxsDpWfXksuY4D9rLvUDzlPw9ztecxigBbWRn7OfAUJoK +czJn5Ty6k4IqEYSfeK1Z1orM4vqdzZy50Op/z0bVA9pqzCzHO1YTGX8UOm5f5ZG wCmBSC+IfnsJpziyENt0Go7CPRtYqYp+JKfo/tLZtG1fWXWUFzc6tn9rR3xAklg9 TaRRX7IIjYlBeilDlS9E+u8XJSXT8BUZZof8afO0YbN3NBwgnJm0yO6cT037LY0r dUd2Dvtc4YVt3e3Tul+WZ3Y9vS2grOgt6mWRNVZAZr7uZeIeDbiwB6+W6XEA/g7Z Kk/UjvSTozUHsit7vRoHbXbVSLXtzlid4V0wZPMTtZxlc5L3lkvUyYoPyIQh/WB8 KIqT7bELR2+khdkSZz90dz41FUieIR9swdUfsqHN6HYRiJOf3NfdloJRW8/4+bTg NgEjOFAeNTK6eyve7FGL7CZAhKftQvvNxgh77fsxCUJDqDIeug8VDhEAB7BCQL4G 5xfHoEMCwrcjEdEXzfEeoKqFCXFvebB4xLyAKQUM6w//aXTfrPE7jVX5ww4Omb48 cARLzj0FrocrTbqgNsFZPhZqc7mkG+uD8wAGejmVeHLgrbVZhamzeTFvYaJyYG6W 5nqEXZ0hvg1IUO4s9yW5BZE77iIu7ZTi9skPSWqhynvqLW98yT/g22FUBBlgH7tf 41pJITDPutIAP5f0e3vQBZWHRNTFe00bGmMRneAsS+WOkOIqIqtVMBL+8OyQBcKJ 0+X8RqWnL/m46UQGJIbrh4TEfA/3nyi0cDOZvLz4mkiAlwk+BhbcxCvdXDLi3qNM iSIUYZdtDbOKqsvcfeW+szz8GTlsIdh9WDC5+GXudj4kqt/mu9bocjrLlroRaaH/ glQVO3psjZOuAFeN01hRp6cL12zd/cesErI/DTif6YyTNlMchfPqZPZ9ZM6APEjE VNnlG+1ah9okCzXSyseokoNDZivV11qgKz+RwfU40M97sSJiGrqfqHMM5e60tIXQ 6MIj2eqV5xCy6ug70uTU6ZStejmaW0e2IM0BB7k78oVVnaxslYFylKdIZ+6GEZlV gogLnoMSK0bDlK+rLrjhQCHAOz36aoMDw2hHE4FQDJVkfrgPfPDM/NkOyxK+qc6p c0MEFiyr5FFgwv4sefOXHiiBM2GgIXeXlJaDUAPQtDfbuVNBcMjXMX+xdDk66MQp HM8I4t9M3JVKvqUwnTlWB4w= =1LVQ -----END PGP PUBLIC KEY BLOCK-----