Merge branch 'vm' of github.com:firstlookmedia/dangerzone into vm

This commit is contained in:
Micah Lee 2021-08-11 15:50:29 -07:00
commit db674a184e
No known key found for this signature in database
GPG key ID: 403C2657CD994F73
10 changed files with 314 additions and 55 deletions

View file

@ -31,7 +31,13 @@ Create a .deb:
Install dependencies:
```sh
sudo dnf install -y rpm-build podman python3 python3-setuptools python3-pyside2 python3-appdirs python3-click python3-pyxdg python3-requests python3-colorama
sudo dnf install -y rpm-build podman python3 python3-setuptools python3-pyside2 python3-appdirs python3-click python3-pyxdg python3-requests python3-colorama python3-psutil
```
Build the latest container:
```sh
./install/linux/build-container.py
```
Run from source tree:
@ -62,7 +68,9 @@ If you don't have it already, install poetry (`pip3 install --user poetry`). The
poetry install
```
Make sure [Docker Desktop](https://www.docker.com/products/docker-desktop) and vagrant (`brew install vagrant`) are installed and run this to collect the binaries from Docker Desktop and then build a custom Alpine Linux ISO for Dangerzone, and copy them into the `share` folder:
Make sure [Docker Desktop](https://www.docker.com/products/docker-desktop) and vagrant (`brew install vagrant`) are installed.
Run this to build a custom Alpine Linux ISO for Dangerzone, and copy it into the `share` folder:
```sh
./install/macos/make-vm.sh
@ -97,8 +105,6 @@ The output is in the `dist` folder.
## Windows
Install [Docker Desktop](https://www.docker.com/products/docker-desktop).
These instructions include adding folders to the path in Windows. To do this, go to Start and type "advanced system settings", and open "View advanced system settings" in the Control Panel. Click Environment Variables. Under "System variables" double-click on Path. From there you can add and remove folders that are available in the PATH.
Download Python 3.9.0, 32-bit (x86) from https://www.python.org/downloads/release/python-390/. I downloaded python-3.9.0.exe. When installing it, make sure to check the "Add Python 3.9 to PATH" checkbox on the first page of the installer.
@ -115,6 +121,18 @@ Change to the `dangerzone` folder, and install the poetry dependencies:
poetry install
```
Make sure these are installed:
- [Docker Desktop](https://www.docker.com/products/docker-desktop)
- [Vagrant](https://www.vagrantup.com/downloads)
- [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
Run this to build a custom Alpine Linux ISO for Dangerzone, and copy it (and some binaries from Docker) into the `share` folder:
```
.\install\windows\make-vm.bat
```
After that you can launch dangerzone during development with:
```

View file

@ -88,10 +88,13 @@ def vm_exec(args, vm_info, stdout_callback=None):
return exec(args, stdout_callback)
def vm_mkdir(vm_info):
def vm_mkdirs(vm_info):
guest_path = os.path.join("/home/user/", str(uuid.uuid4()))
vm_exec(["/bin/mkdir", guest_path], vm_info)
return guest_path
input_dir = os.path.join(guest_path, "input")
pixel_dir = os.path.join(guest_path, "pixel")
safe_dir = os.path.join(guest_path, "safe")
vm_exec(["/bin/mkdir", guest_path, input_dir, pixel_dir, safe_dir], vm_info)
return guest_path, input_dir, pixel_dir, safe_dir
def vm_rmdir(guest_path, vm_info):
@ -154,13 +157,9 @@ def convert(global_common, input_filename, output_filename, ocr_lang, stdout_cal
# Otherwise, create temp dirs
if vm_info:
ssh_args_str = " ".join(pipes.quote(s) for s in vm_ssh_args(vm_info))
print("If you want to SSH to the VM: " + ssh_args_str)
guest_tmpdir = vm_mkdir(vm_info)
input_dir = os.path.join(guest_tmpdir, "input")
pixel_dir = os.path.join(guest_tmpdir, "pixel")
safe_dir = os.path.join(guest_tmpdir, "safe")
print("\nIf you want to SSH to the VM:\n" + ssh_args_str + "\n")
guest_tmpdir, input_dir, pixel_dir, safe_dir = vm_mkdirs(vm_info)
guest_input_filename = os.path.join(input_dir, "input_file")
container_output_filename = os.path.join(safe_dir, "safe-output-compressed.pdf")

View file

@ -38,6 +38,9 @@ class GuiCommon(object):
# Preload list of PDF viewers on computer
self.pdf_viewers = self._find_pdf_viewers()
# Are we done waiting (for VM to start, or container to install)
self.is_waiting_finished = False
def get_window_icon(self):
if platform.system() == "Windows":
path = self.global_common.get_resource_path("dangerzone.ico")

View file

@ -54,6 +54,10 @@ class MainWindow(QtWidgets.QMainWindow):
self.content_widget.close_window.connect(self.close)
# Only use the waiting widget if we have a VM
if self.gui_common.is_waiting_finished:
self.waiting_widget.hide()
self.content_widget.show()
else:
self.waiting_widget.show()
self.content_widget.hide()
@ -70,6 +74,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.show()
def waiting_finished(self):
self.gui_common.is_waiting_finished = True
self.waiting_widget.hide()
self.content_widget.show()
@ -423,13 +428,14 @@ class SettingsWidget(QtWidgets.QWidget):
class ConvertThread(QtCore.QThread):
finished = QtCore.Signal()
finished = QtCore.Signal(bool)
update = QtCore.Signal(bool, str, int)
def __init__(self, global_common, common):
super(ConvertThread, self).__init__()
self.global_common = global_common
self.common = common
self.error = False
def run(self):
ocr_lang = self.global_common.ocr_languages[
@ -443,19 +449,20 @@ class ConvertThread(QtCore.QThread):
ocr_lang,
self.stdout_callback,
):
self.finished.emit()
self.finished.emit(self.error)
def stdout_callback(self, line):
try:
status = json.loads(line)
except:
print(f"Invalid JSON returned from container: {line}")
self.error = True
self.update.emit(True, "Invalid JSON returned from container", 0)
return
s = Style.BRIGHT + Fore.CYAN + f"{status['percentage']}% "
if status["error"]:
self.error = True
s += Style.RESET_ALL + Fore.RED + status["text"]
else:
s += Style.RESET_ALL + status["text"]
@ -473,6 +480,8 @@ class ConvertWidget(QtWidgets.QWidget):
self.gui_common = gui_common
self.common = common
self.error = False
# Dangerous document label
self.dangerous_doc_label = QtWidgets.QLabel()
self.dangerous_doc_label.setAlignment(QtCore.Qt.AlignCenter)
@ -480,11 +489,25 @@ class ConvertWidget(QtWidgets.QWidget):
"QLabel { font-size: 16px; font-weight: bold; }"
)
# Label
self.error_image = QtWidgets.QLabel()
self.error_image.setPixmap(
QtGui.QPixmap.fromImage(
QtGui.QImage(self.global_common.get_resource_path("error.png"))
)
)
self.error_image.hide()
self.label = QtWidgets.QLabel()
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setWordWrap(True)
self.label.setStyleSheet("QLabel { font-size: 18px; }")
label_layout = QtWidgets.QHBoxLayout()
label_layout.addWidget(self.error_image)
label_layout.addWidget(self.label, stretch=1)
# Progress bar
self.progress = QtWidgets.QProgressBar()
self.progress.setRange(0, 100)
self.progress.setValue(0)
@ -493,7 +516,7 @@ class ConvertWidget(QtWidgets.QWidget):
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.dangerous_doc_label)
layout.addStretch()
layout.addWidget(self.label)
layout.addLayout(label_layout)
layout.addWidget(self.progress)
layout.addStretch()
self.setLayout(layout)
@ -512,13 +535,17 @@ class ConvertWidget(QtWidgets.QWidget):
def update(self, error, text, percentage):
if error:
# TODO: add error image or something
pass
self.error = True
self.error_image.show()
self.progress.hide()
self.label.setText(text)
self.progress.setValue(percentage)
def all_done(self):
if self.error:
return
# In Windows, open Explorer with the safe PDF in focus
if platform.system() == "Windows":
dest_filename_windows = self.common.output_filename.replace("/", "\\")

View file

@ -1,5 +1,4 @@
import os
import sys
import subprocess
import uuid
import pipes
@ -10,6 +9,8 @@ import getpass
import json
import psutil
import time
import platform
import shutil
from PySide2 import QtCore
@ -32,24 +33,8 @@ class Vm(QtCore.QObject):
self.sshd_port = None
self.sshd_tunnel_port = None
# Processes
self.vpnkit_p = None
self.hyperkit_p = None
self.devnull = open(os.devnull, "w")
# Relevant paths
self.vpnkit_path = self.global_common.get_resource_path("bin/vpnkit")
self.hyperkit_path = self.global_common.get_resource_path("bin/hyperkit")
self.vm_iso_path = self.global_common.get_resource_path("vm/dangerzone.iso")
self.vm_kernel_path = self.global_common.get_resource_path("vm/kernel")
self.vm_initramfs_path = self.global_common.get_resource_path(
"vm/initramfs.img"
)
# Folder to hold temporary files related to the VM
self.state_dir = tempfile.TemporaryDirectory()
self.vpnkit_sock_path = os.path.join(self.state_dir.name, "vpnkit.eth.sock")
self.hyperkit_pid_path = os.path.join(self.state_dir.name, "hyperkit.pid")
self.ssh_host_key_path = os.path.join(self.state_dir.name, "host_ed25519")
self.ssh_host_pubkey_path = os.path.join(
self.state_dir.name, "host_ed25519.pub"
@ -63,12 +48,47 @@ class Vm(QtCore.QObject):
self.vm_info_path = os.path.join(self.state_dir.name, "info.json")
self.vm_disk_img_path = os.path.join(self.state_dir.name, "disk.img")
self.vm_iso_path = self.global_common.get_resource_path("vm/dangerzone.iso")
if platform.system() == "Darwin":
self.ssh_keygen_path = shutil.which("ssh-keygen")
self.sshd_path = shutil.which("sshd")
# Processes
self.vpnkit_p = None
self.hyperkit_p = None
self.devnull = open(os.devnull, "w")
# Relevant paths
self.vpnkit_path = self.global_common.get_resource_path("bin/vpnkit")
self.hyperkit_path = self.global_common.get_resource_path("bin/hyperkit")
self.vm_kernel_path = self.global_common.get_resource_path("vm/kernel")
self.vm_initramfs_path = self.global_common.get_resource_path(
"vm/initramfs.img"
)
# Temporary files related to the VM
self.vpnkit_sock_path = os.path.join(self.state_dir.name, "vpnkit.eth.sock")
self.hyperkit_pid_path = os.path.join(self.state_dir.name, "hyperkit.pid")
# UDID for VM
self.vm_uuid = str(uuid.uuid4())
self.vm_cmdline = (
"earlyprintk=serial console=ttyS0 modules=loop,squashfs,sd-mod"
)
if platform.system() == "Windows":
self.vboxmanage_path = (
"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe"
)
self.ssh_keygen_path = os.path.join(
self.global_common.get_resource_path("bin"), "ssh-keygen.exe"
)
self.sshd_path = os.path.join(
self.global_common.get_resource_path("bin"), "sshd.exe"
)
# Threads
self.wait_t = None
@ -76,8 +96,7 @@ class Vm(QtCore.QObject):
self.stop()
def start(self):
self.state = self.STATE_STARTING
self.vm_state_change.emit(self.state)
print("Starting VM\n")
# Delete keys if they already exist
for filename in [
@ -89,10 +108,14 @@ class Vm(QtCore.QObject):
if os.path.exists(filename):
os.remove(filename)
# Find an open port
self.sshd_port = self.find_open_port()
self.sshd_tunnel_port = self.find_open_port()
# Generate new keys
subprocess.run(
[
"/usr/bin/ssh-keygen",
self.ssh_keygen_path,
"-t",
"ed25519",
"-C",
@ -107,7 +130,7 @@ class Vm(QtCore.QObject):
)
subprocess.run(
[
"/usr/bin/ssh-keygen",
self.ssh_keygen_path,
"-t",
"ed25519",
"-C",
@ -125,13 +148,9 @@ class Vm(QtCore.QObject):
with open(self.ssh_client_pubkey_path) as f:
ssh_client_pubkey = f.read()
# Find an open port
self.sshd_port = self.find_open_port()
self.sshd_tunnel_port = self.find_open_port()
# Start an sshd service on this port
args = [
"/usr/sbin/sshd",
self.sshd_path,
"-4",
"-E",
self.sshd_log_path,
@ -160,6 +179,16 @@ class Vm(QtCore.QObject):
print("> " + args_str)
subprocess.run(args, stdout=self.devnull, stderr=self.devnull)
if platform.system() == "Darwin":
self.start_macos()
if platform.system() == "Windows":
self.start_windows()
def start_macos(self):
self.state = self.STATE_STARTING
self.vm_state_change.emit(self.state)
# Create a JSON object to pass into the VM
# This is a 512kb file that starts with a JSON object, followed by null bytes
guest_vm_info = {
@ -246,6 +275,84 @@ class Vm(QtCore.QObject):
self.wait_t.timeout.connect(self.vm_timeout)
self.wait_t.start()
def start_windows(self):
vm_name = "dangezone-podman"
basefolder_path = os.path.join(
self.global_common.appdata_path, "virtualbox-basefolder"
)
# See if we already have a VM
exists = False
for line in subprocess.check_output([self.vboxmanage_path, "list", "vms"]):
name = line.split()[0].lstrip('"').rstrip('"')
if name == vm_name:
exists = True
break
# Create the VM
if not exists:
subprocess.run(
[
self.vboxmanage_path,
"createvm",
"--name",
vm_name,
"--basefolder",
basefolder_path,
"--ostype",
"Linux_x64",
"--register",
]
)
# Configure the VM
subprocess.run(
[
self.vboxmanage_path,
"modifyvm",
vm_name,
"--memory",
"4096",
"--nic1",
"nat",
"--cableconnected1",
"on",
]
)
subprocess.run(
[
self.vboxmanage_path,
"storagectl",
vm_name,
"--name",
"DangerzoneBoot",
"--add",
"ide",
"--bootable",
"on",
]
)
subprocess.run(
[
self.vboxmanage_path,
"storageattach",
vm_name,
"--storagectl",
"DangerzoneBoot",
"--port",
"1",
"--device",
"1",
"--type",
"dvddrive",
"--medium",
self.vm_iso_path,
]
)
# Start the VM
subprocess.run([self.vboxmanage_path, "startvm", "--type", "headless"])
def vm_connected(self):
self.state = self.STATE_ON
self.vm_state_change.emit(self.state)
@ -255,8 +362,16 @@ class Vm(QtCore.QObject):
self.vm_state_change.emit(self.state)
def stop(self):
# Kill existing processes
self.kill_sshd()
if platform.system() == "Darwin":
self.stop_macos()
if platform.system() == "Windows":
self.stop_windows()
def stop_macos(self):
# Kill existing processes
if self.vpnkit_p is not None:
self.vpnkit_p.terminate()
self.vpnkit_p = None
@ -267,6 +382,17 @@ class Vm(QtCore.QObject):
# Just to be extra sure
self.kill_hyperkit()
def stop_windows(self):
vm_name = "dangezone-podman"
subprocess.run(
[
self.vboxmanage_path,
"controlvm",
vm_name,
"poweroff",
]
)
def find_open_port(self):
with socket.socket() as tmpsock:
while True:

View file

@ -1,2 +0,0 @@
set DANGERZONE_MODE=container
poetry run python .\dev_scripts\dangerzone %*

View file

@ -0,0 +1,12 @@
#!/bin/sh
VAGRANT_FILES=$(find /vagrant -type f | grep -v /vagrant/.vagrant | grep -v /vagrant/vm)
DANGERZONE_CONVERTER_FILES=$(find /opt/dangerzone-converter -type f)
for FILE in $VAGRANT_FILES; do dos2unix $FILE; done
for FILE in $DANGERZONE_CONVERTER_FILES; do dos2unix $FILE; done
/vagrant/build-iso.sh
for FILE in $VAGRANT_FILES; do unix2dos $FILE; done
for FILE in $DANGERZONE_CONVERTER_FILES; do unix2dos $FILE; done

View file

@ -0,0 +1,65 @@
import os
import sys
import inspect
import requests
import hashlib
import zipfile
import shutil
def main():
zip_url = "https://github.com/PowerShell/Win32-OpenSSH/releases/download/V8.6.0.0p1-Beta/OpenSSH-Win32.zip"
expected_zip_sha256 = (
"0221324212413a6caf260f95e308d22f8c141fc37727b622a6ad50998c46d226"
)
# Figure out the paths
root_path = os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
)
zip_path = os.path.join(root_path, "build", "OpenSSH-Win32.zip")
extracted_path = os.path.join(root_path, "build", "OpenSSH-Win32")
bin_path = os.path.join(root_path, "share", "bin")
os.makedirs(os.path.join(root_path, "build"), exist_ok=True)
os.makedirs(os.path.join(bin_path), exist_ok=True)
# Make sure openssh is downloaded
if not os.path.exists(zip_path):
print(f"Downloading {zip_url}")
r = requests.get(zip_url)
open(zip_path, "wb").write(r.content)
zip_sha256 = hashlib.sha256(r.content).hexdigest()
else:
zip_data = open(zip_path, "rb").read()
zip_sha256 = hashlib.sha256(zip_data).hexdigest()
# Compare the hash
if zip_sha256 != expected_zip_sha256:
print("ERROR! The sha256 doesn't match:")
print("expected: {}".format(expected_zip_sha256))
print(" actual: {}".format(zip_sha256))
sys.exit(-1)
# Extract the zip
with zipfile.ZipFile(zip_path, "r") as z:
z.extractall(os.path.join(root_path, "build"))
# Copy binaries to share
shutil.copy(os.path.join(extracted_path, "libcrypto.dll"), bin_path)
shutil.copy(os.path.join(extracted_path, "moduli"), bin_path)
shutil.copy(os.path.join(extracted_path, "scp.exe"), bin_path)
shutil.copy(os.path.join(extracted_path, "ssh-agent.exe"), bin_path)
shutil.copy(os.path.join(extracted_path, "ssh-keygen.exe"), bin_path)
shutil.copy(os.path.join(extracted_path, "ssh.exe"), bin_path)
shutil.copy(os.path.join(extracted_path, "sshd.exe"), bin_path)
shutil.copyfile(
os.path.join(extracted_path, "LICENSE.txt"),
os.path.join(bin_path, "LICENSE-OpenSSH.txt"),
)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,11 @@
REM Build ISO
cd install\vm-builder
vagrant up
vagrant ssh -- dos2unix /vagrant/windows.sh
vagrant ssh -- /vagrant/windows.sh
vagrant halt
cd ..\..
REM Copy the ISO to resources
mkdir share\vm
cp install\vm-builder\vm\dangerzone.iso share\vm

BIN
share/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB