diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3244657 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.swp +*.gz +__pycache__ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..72afd78 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,23 @@ +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +stages: + - Static Analysis + +shellcheck: + image: koalaman/shellcheck-alpine:latest + stage: Static Analysis + before_script: + - shellcheck --version + script: + - bash -c 'shopt -s globstar nullglob; shellcheck --external-sources --source-path=SCRIPTDIR **/*.sh' + +pylint: + image: python:latest + stage: Static Analysis + before_script: + - python --version + - pylint --vesrion + - pip install -r ci/requirements.txt + script: + - bash -c 'shopt -s globstar nullglob; pylint3 --exit-zero **/*.py' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0916709 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2021 Elliot Killick + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4251c23 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +PKGNAME = qubes-video-companion + +BINDIR ?= /usr/bin +DATADIR ?= /usr/share +QREXECDIR ?= /etc/qubes-rpc + +INSTALL_DIR = install -d +INSTALL_PROGRAM = install -D +INSTALL_DATA = install -Dm 644 + +help: + @echo "make build Build components" + @echo "make install-vm Install all components necessary for VMs" + @echo "make install-dom0 Install all components necessary for dom0" + @echo "make install-both Install components necessary for VMs and dom0" + @echo "make install-policy Install qrexec policies" + @echo "make install-license Install license to $(DATADIR)/licenses/$(PKGNAME)" + @echo "make clean Clean build" + +build: + $(MAKE) -C doc manpages + +install-vm: install-both + $(INSTALL_DIR) $(DESTDIR)$(BINDIR) + $(INSTALL_PROGRAM) video/$(PKGNAME) $(DESTDIR)$(BINDIR) + $(INSTALL_DIR) $(DESTDIR)$(DATADIR)/$(PKGNAME)/video + $(INSTALL_PROGRAM) video/setup.sh video/receiver.sh video/destroy.sh video/common.sh $(DESTDIR)$(DATADIR)/$(PKGNAME)/video + $(INSTALL_DATA) scripts/webcam.html $(DESTDIR)$(DATADIR)/$(PKGNAME)/scripts + $(INSTALL_DIR) $(DESTDIR)$(DATADIR)/$(PKGNAME)/scripts/v4l2loopback + $(INSTALL_PROGRAM) scripts/v4l2loopback/install.sh $(DESTDIR)$(DATADIR)/$(PKGNAME)/scripts/v4l2loopback + $(INSTALL_DATA) scripts/v4l2loopback/author.asc $(DESTDIR)$(DATADIR)/$(PKGNAME)/scripts/v4l2loopback + $(MAKE) -C doc install + +install-dom0: install-both install-policy + +install-both: + $(INSTALL_DIR) $(DESTDIR)$(QREXECDIR) + $(INSTALL_PROGRAM) qubes-rpc/services/qvc.Webcam qubes-rpc/services/qvc.ScreenShare $(DESTDIR)$(QREXECDIR) + $(INSTALL_DIR) $(DESTDIR)$(DATADIR)/$(PKGNAME)/ui + $(INSTALL_PROGRAM) ui/*.py ui/*.sh $(DESTDIR)$(DATADIR)/$(PKGNAME)/ui + $(INSTALL_DIR) $(DESTDIR)$(DATADIR)/$(PKGNAME)/scripts + $(INSTALL_PROGRAM) scripts/set-webcam-format.sh $(DESTDIR)$(DATADIR)/$(PKGNAME)/scripts + $(INSTALL_DIR) $(DESTDIR)$(DATADIR)/doc/$(PKGNAME) + $(INSTALL_DATA) README.md doc/pipeline-design.md $(DESTDIR)$(DATADIR)/doc/$(PKGNAME) + $(INSTALL_DIR) $(DESTDIR)$(DATADIR)/doc/$(PKGNAME)/visualizations + $(INSTALL_DATA) doc/visualizations/* $(DESTDIR)$(DATADIR)/doc/$(PKGNAME)/visualizations + +install-policy: + $(INSTALL_DIR) $(DESTDIR)$(QREXECDIR)/policy + $(INSTALL_DATA) qubes-rpc/policies/* $(DESTDIR)$(QREXECDIR)/policy + +install-license: + $(INSTALL_DIR) $(DESTDIR)$(DATADIR)/licenses/$(PKGNAME) + $(INSTALL_DATA) LICENSE $(DESTDIR)$(DATADIR)/licenses/$(PKGNAME) + +clean: + $(MAKE) -C doc clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..646e859 --- /dev/null +++ b/README.md @@ -0,0 +1,213 @@ +# Qubes Video Companion + +
+ +
+ + Logo + +
+ +
+ GitLab pipeline + Commits since latest release + + License + +
+ +## About + +Qubes Video Companion is a tool for securely streaming webcams and sharing screens across virtual machines. + +It accomplishes this by creating a uni-directional flow of raw video that is passed from one virtual machine to another through file descriptors thereby allowing both machines to be completely air-gapped with no networking stacks exposed. This design makes the side of the video sending virtual machine 100% immune to attack and only leaves a very small attack surface on the side of the video receiving virtual machine. + +The project emphasizes correctness and security all the while also sporting superb performance by maintaining a small footprint of the available computational resources and low latency even at Full HD and greater resolutions at 30 or more frames per second. + +## Installation + +**WIP: Currently in the process of becoming part of the Qubes Contribution Repo** + +Qubes Video Companion is available for installation on Qubes OS in packaged form for both Fedora (.rpm) and Debian (.deb). To get it, simply add the Qubes Contribution Repo to the list of available repositories and install the `qubes-video-companion` package! + +For security reasons, please refrain from installing the Qubes Video Companion package from GitHub Releases, those are just there for show. + +### Run the following commands in Dom0 (AdminVM) +1. `sudo qubes-dom0-update qubes-repo-contrib` +2. `sudo qubes-dom0-update --clean qubes-video-companion` + +### Run the following commands in a TemplateVM + +#### Fedora +1. `sudo dnf install qubes-repo-contrib` +2. `sudo dnf install qubes-video-companion` + +#### Debian +1. `sudo apt update` +2. `sudo apt install qubes-repo-contrib` +3. `sudo apt install qubes-video-companion` + +## Usage + +### Webcam + +Simply run the following command in the virtual machine of the webcam stream recipient: + +`qubes-video-companion webcam` + +A secure confirmation dialog will appear asking where the webcam stream is to be sourced from. If the webcam device is attached to `sys-usb` then select that qube as the target, if instead the webcam is attached to `dom0` then select that as the target. Afterwards, confirm the operation by clicking `OK`. + +### Screen Sharing + +Simply run the following command in the virtual machine of the screen sharing recipient: + +`qubes-video-companion screenshare` + +A secure confirmation dialog will appear asking where the screen to share is to be sourced from. Select any qube as the target screen, this could be a regular unprivileged qube such as `personal` or a [DisposableVM](https://www.qubes-os.org/doc/disposablevm/), or an ultimately trusted one such as `dom0` (caution is advised to avoid information disclosure. Afterwards, confirm the operation by clicking `OK`. + +Note that confirmation isn't required when a VM wants to view the screen of a DisposableVM it launched itself because the parent VM already has full control over the DisposableVM. + +### Preview + +At this point, install and open an application such as Cheese (packaged as: `cheese`) to preview the webcam or screen shared video stream. You're all set! + +## Security + +Here is a review of the security concerns webcams entail that Qubes users either no longer have to worry about or have been greatly mitigated thanks to Qubes Video Companion. It also goes over some of the lengths this project went to in order to create a ["reasonably secure"](https://www.qubes-os.org) end product. + +- An unspoofable system tray icon and notification appears when webcam streaming or screen sharing is taking place to serve as a clear and persistent indication to the user of what is being shared + - This tray icon and notification is created by the video sender so it's impossible for an attacker to forcefully obfuscate or remove it + - This is especially important for screen sharing because whereas most webcams have an indicator light to inform the user of when they are in use; screen sharing has no such mechanism +- One-way only communication from the video sending machine to the video receiving machine thus guaranteeing the security of the sender + - This is absolutely crucial because the sender, typically `sys-usb`, has a lot of exposed hypervisor and hardware attack surface + - This is due to having all USB devices in the form of the entire [USB controller PCI device](https://www.qubes-os.org/doc/device-handling-security/#pci-security) passed through to it + - Additionally, for PCI passthrough `sys-usb` must be an HVM (as opposed to a PVH) domain which is far more complex and has proven to be the source of many more bugs + - These vulnerabilities are very real and common, case in point: + - XSA-325 of [QSB #063-2020](https://github.com/QubesOS/qubes-secpack/blob/master/QSBs/qsb-063-2020.txt): HVM domains such as `sys-usb` may cause a use-after-free leading to a crash for which privilege escalation cannot be ruled out + - XSA-337 of [QSB #059-2020](https://github.com/QubesOS/qubes-secpack/blob/master/QSBs/qsb-059-2020.txt): "`A malicious HVM with a PCI device (such as sys-net or sys-usb in the default Qubes OS configuration) can potentially compromise the whole system.`" + - XSA-321 of [QSB #058-2020](https://github.com/QubesOS/qubes-secpack/blob/master/QSBs/qsb-058-2020.txt): A vulnerability with the same level of impact as the previous one this time only affecting Intel processors + - If a USB keyboard and mouse is in use (as opposed to PS/2 peripherals commonly used internally in laptops), `sys-usb` is responsible for routing the input of those devices to `dom0` + - In this scenario, `sys-usb` compromise is essentially equivalent to `dom0` compromise through keyboard/mouse input injection thus making it all the more important that we protect `sys-usb` + - For example, an attacker could quickly inject the global default XFCE keyboard shortcut `ALT+F2` to open `xfce4-appfinder --collapsed` followed by an arbitrary command to gain control of `dom0` + - Other times, this is of ultimate concern because the most highly privileged virtual machine in the system, `dom0`, is in control of the USB devices + - Although, passing through of individual USB devices with USBIP over Qubes RPC from `dom0` was never a possibility in Qubes for security reasons +- The attack surface of the video receiving machine is crafted to be as small as possible + - GStreamer [`capsfilter`](https://gstreamer.freedesktop.org/documentation/coreelements/capsfilter.html) (capabilities filter) is used to create a singular, small, homogeneous attack surface for accepting video + - Video dimensions and frame rate capabilities are sanitized upon being pass to the video receiver to prevent the injection of additional capabilities + - Only high quality and robust GStreamer ["Base" and "Good" plugins](https://gitlab.freedesktop.org/gstreamer/gstreamer/-/raw/master/README) are used to minimize the possibility of bugs + - Additionally, only the most common and battle-hardened GStreamer components and formats are used + - Any complex operations (such as video conversions where necessary) are done on the side of the sending machine where the video data is still inherently trusted + - Only small parts of two kernel modules ever touch untrusted video data: [`v4l2loopback`](https://github.com/umlaeute/v4l2loopback) and the Video4Linux2 (V4L2) driver in mainline Linux + - This is a massive improvement to what it would have been previously using USBIP over Qubes RPC for passing through a USB webcam device thereby exposing the entire Linux USB stack to a potential attacker + - Kernel modules are reloaded between Qubes Video Companion sessions to ensure a clean state +- The video dimensions and FPS are sanitized by the video receiver to ensure they contain no extraneous GStreamer capabilities +- No TCP/IP networking stacks are exposed + - Communication is performed through file descriptors that run between virtual machines using the simple point-to-point protocol that is [Qubes RPC](https://www.qubes-os.org/doc/qrexec/) (built upon Xen vchan under the hood) +- Mitigates vulnerabilities in the webcam firmware + - With Qubes Video Companion, the virtual machine receiving the webcam video stream never has direct hardware access to the webcam device thus rendering firmware tinkering impossible + - Firmware vulnerabilities as demonstrated in [this paper](https://www.usenix.org/system/files/conference/usenixsecurity14/sec14-paper-brocker.pdf) could allow an attacker to disable the webcam LED indicator or even perform a VM escape (or at least an escape to `sys-usb`) + - Here is a good [WIRED article](https://www.wired.com/story/firmware-hacks-vulnerable-pc-components-peripherals/) on the state of firmware security +- Ability to separate the video and microphone capabilities that many webcams feature + - Webcam microphones are often very sensitive picking up lots of background noise which serves as an information leak should the virtual machine it's being passed through be compromised + - This mitigates listening to keystrokes as a method of keylogging the entire system + - Public implementations for conducting this type of attack in practice [already exist](https://github.com/shoyo/acoustic-keylogger) + - This is also a mitigation for other [acoustic side-channel attacks](https://en.wikipedia.org/wiki/Acoustic_cryptanalysis) + - Removes possibility of hearing background conversations + - Instead, connect only the desired microphone such as one that comes part of a headset or even none at all using PulseAudio (please see the FAQ) +- Conscious effort was made to base this project off of software with good security track record + - GStreamer instead of FFmpeg +- Allows for better isolation of complex (and often proprietary) video conferencing software often used with webcams that is commonly the subject of many critical vulnerabilities (e.g. Zoom) +- As has been well noted [here](https://github.com/QubesOS/qubes-issues/issues/2079#issuecomment-226942065), this project does _not_ solve the privacy issue of an already compromised (as a result of a malicious USB device) `sys-usb` sniffing the webcam video stream and leaking that data to other USB devices + - Leaking data over the network is already prevented through air-gapping `sys-usb` + - However, the unidirectional communication of video data from `sys-usb` this project institutes 100% guarantees that it cannot be compromised in the first place through the use of a webcam + - This assumes that the webcam itself isn't backdoored perhaps through a [supply chain attack](https://en.wikipedia.org/wiki/Supply_chain_attack) which have been on the rise lately (e.g. SolarWinds compromise) +- All GitHub releases are signed by the project maintainer's PGP key + - All commits by the maintainer are also always signed with their PGP key + - Should signing ever cease, assume compromise + - Current maintainer: [Elliot Killick](https://github.com/elliotkillick) PGP key + - PGP key: 018F B9DE 6DFA 13FB 18FB 5552 F9B9 0D44 F83D D5F2 + +## Frequently Asked Questions (FAQ) + +### What about audio? + +In Qubes OS, audio is handled securely through the use of simple PulseAudio sinks. + +The microphone of a webcam can be utilized by configuring it as the recording device for the desired qube in the `Recording` tab of the `Volume Control` application in `dom0`. Then, simply connect the microphone to the desired qube in the `Qubes Devices` system tray app at the bottom-right of the screen. + +Audio is out of scope for this project in particular. + +### Why can I perceive some latency in the video playback? + +This means the CPU is at its limit (nearing or at 100% usage). To check this, install GNOME System Monitor (packaged as `gnome-system-monitor`) in the video receiving VM and assess the CPU usage for each of the processes and overall in the resources graph. + +It's important to remember that for security reasons, qubes do not have access to the GPU and so therefore must rely entirely on the CPU even for graphical workloads. + +To fix the latency, do one or more of the following: + +1. Remove or shrink the size of any windows playing back the video stream being shared to the video receiving VM + - Just like in video editing, playing back raw, unencoded video in realtime is a very computationally expensive task that takes much more processing power than recording and in this case even sharing the video across VMs combined + - This issue is magnified when no GPU is available + - E.g. For OBS, right-click the video preview and uncheck `Enable preview` when recording +2. Pause any videos from playing (e.g. YouTube videos) in any of the VMs +3. Assign more vCPUs to the video receiving VM + - The video rendering workload lends quite nicely to multithreading so the more vCPUs the better + - The vCPU count can be changed in the settings for that qube +4. Reduce the resolution and/or frame rate the webcam is recording at to a supported lower quality setting + - Refer to [set-webcam-format.sh](scripts/set-webcam-format.sh) + - This script is installed in: `/usr/share/qubes-video-companion/scripts` + +### Why GStreamer instead of FFmpeg? + +- GStreamer has a much better security track record than FFmpeg + - Even a broad search for all CVEs containing the term ["GStreamer"](https://nvd.nist.gov/vuln/search/results?query=gstreamer) shows far less vulnerabilities than what has specifically been assigned to the [FFmpeg project](https://www.cvedetails.com/product/6315/Ffmpeg-Ffmpeg.html?vendor_id=3611) +- FFmpeg is not in included in the base Fedora repositories due to patenting issues + - Where FFmpeg is monolithic; GStreamer is modular allowing for the patent unencumbered components to be included in base Fedora repositories +- GStreamer (at least the base and good plugins which is all we're using) has more efficient, clean and performant code +- GStreamer has better cross-platform support for future development +- GStreamer has superior documentation +- FFmpeg has its advantages in some areas, but for this project, GStreamer is the way to go + - FFmpeg is more flexible and easier to learn (more "magic") + - It has better and more special effects for video editing + +### How does it work? + +A basic overview is provided in the About section of this `README`. + +For information on the pipeline, check out `doc/pipeline-design.md` and the accompanying diagrams in `doc/visualizations`. This will provide insight to the thought process that went into some of the design decisions made in this project along with illustrations of the pipeline internals. + +The user interface components are created with GTK 3, GObject and AppIndicator (because GTK deprecated `GtkStatusIcon`). + +## Contributing + +You can start by giving this project a star! High quality PRs are also welcome! Take a look at the issue tracker if you're looking for things that need improvement. Other improvements such as more elegant ways of completing a task, code cleanup and other fixes are also welcome. + +Alternatively, for ideas on what you can contribute to the wider Qubes OS project, take a look at the ["help wanted"](https://github.com/QubesOS/qubes-issues/labels/help%20wanted) Qubes issues and available [GSoC projects](https://www.qubes-os.org/gsoc/). + +The [video camera icon](https://commons.wikimedia.org/wiki/File:Video_camera_icon.svg) used in the project logo is by Рытикова Людмила licensed under the [Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/) license. As a result, the logo as a whole is under the same license. The portion of the logo surrounding the video camera icon is by Max Andersen, used with written permission. + +This project is the product of an independent effort that is not officially endorsed by Qubes OS. + +## Tested Working Applications + +- [Cheese](https://wiki.gnome.org/Apps/Cheese) +- [Chromium](https://www.chromium.org) +- [Open Broadcaster Software (OBS)](https://obsproject.com/) + - For the `Video Capture Device (V4L2)` source click on the `Properties` settings cog and use video format `Planar YUV 4:2:0` (as opposed to `YV12 (Emulated)`) and uncheck `Use buffering` for optimal performance +- [Zoom](https://zoom.us) + +## Webcam Hardware Compatibility List (HCL) + +The model of a given webcam can be found by running `v4l2-ctl --list-devices` in the virtual machine with the webcam device attached. + +- Logitech C922 Pro Stream Webcam + +## End Goals + +- [x] Implement a solution for secure webcam utilization that sufficiently addresses the concerns brought up in this in-depth paper on webcam firmware security: [iSeeYou: Disabling the MacBook Webcam Indicator LED](https://www.usenix.org/system/files/conference/usenixsecurity14/sec14-paper-brocker.pdf) ([Google Scholar](https://scholar.google.ca/scholar?q=iSeeYou%3A+Disabling+the+MacBook+Webcam+Indicator)) by Johns Hopkins University + - The end product should be capable of fully protecting at risk individuals such as those documented in the case studies of this [MIT paper](https://courses.csail.mit.edu/6.857/2014/files/03-jayaram-lui-nguyen-zakarian-preventing-covert-webcam-hacking) in practice (assuming they are Qubes users) +- [x] Effectively solve two long-standing Qubes issues making using a webcam in Qubes OS a painstaking, insecure and computationally expensive (in CPU and RAM!) experience: + - Cannot use a USB camera: [#4035](https://github.com/QubesOS/qubes-issues/issues/4035) + - Feature: Trusted stream for webcam input: [#2079](https://github.com/QubesOS/qubes-issues/issues/2079) +- [x] Add secure screen sharing functionality between qubes + - Feature: Screen sharing between AppVMs: [#6426](https://github.com/QubesOS/qubes-issues/issues/6426) diff --git a/ci/requirements.txt b/ci/requirements.txt new file mode 100644 index 0000000..7afc383 --- /dev/null +++ b/ci/requirements.txt @@ -0,0 +1,3 @@ +# WARNING: These requirements are for use by the CI system only (gitlab.com) +# Otherwise, use the system package manager to ensure utmost security +PyGObject diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..140ee6e --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +qubes-video-companion (1.0.0-1) unstable; urgency=low + + * Initial release + + -- Elliot Killick Thu, 18 Feb 2021 11:40:26 -0500 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..9ce7533 --- /dev/null +++ b/debian/control @@ -0,0 +1,34 @@ +Source: qubes-video-companion +Section: video +Priority: optional +Maintainer: Elliot Killick +Build-Depends: debhelper-compat (= 12), pandoc +Standards-Version: 4.5.0 +Homepage: https://github.com/elliotkillick/qubes-video-companion + +Package: qubes-video-companion +Architecture: all +Multi-Arch: foreign +Depends: gir1.2-ayatanaappindicator3-0.1, + gstreamer1.0-plugins-good, + gstreamer1.0-tools, + python3, + ${misc:Depends} +Description: Securely stream webcams and share screens across virtual machines + Qubes Video Companion is a tool for securely streaming webcams and sharing + screens across virtual machines. + . + It accomplishes this by creating a uni-directional flow of raw video that is + passed from one virtual machine to another through file descriptors thereby + allowing both machines to be completely air-gapped with no networking stacks + exposed. This design makes the side of the video sending virtual machine 100% + immune to attack and only leaves a very small attack surface on the side of the + video receiving virtual machine. + . + The project emphasizes correctness and security all the while also sporting + superb performance by maintaining a small footprint of the available + computational resources and low latency even at Full HD and greater resolutions + at 30 or more frames per second. + . + This package contains all components of Qubes Video companion excluding the + Qubes RPC policies which dom0 enforces. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..9fe8655 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,29 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: Qubes Video Companion +Upstream-Contact: Elliot Killick +Source: https://github.com/elliotkillick/qubes-video-companion + +Files: * +Copyright: 2021 Elliot Killick +License: MIT + MIT License + . + Copyright (C) 2021 Elliot Killick + . + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..0321e39 --- /dev/null +++ b/debian/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f + +%: + dh $@ + +override_dh_auto_install: + make install-vm DESTDIR=$(shell readlink -f .)/debian/qubes-video-companion diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..5425036 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,30 @@ +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +DATADIR ?= /usr/share +MANDIR ?= $(DATADIR)/man +MAN1DIR ?= $(MANDIR)/man1 + +INSTALL_DIR = install -d +INSTALL_DATA = install -Dm 644 + +MANPAGES=$(patsubst %.rst,%.1.gz,$(wildcard *.rst)) + +help: + @echo "make manpages Generate manpages" + @echo "make install Generate manpages and install them to $(MAN1DIR)" + +manpages: $(MANPAGES) + +%.1: %.rst + pandoc -s -f rst -t man -o $@ $< + +%.1.gz: %.1 + gzip -f $< + +install: manpages + $(INSTALL_DIR) $(DESTDIR)$(MAN1DIR) + $(INSTALL_DATA) $(MANPAGES) $(DESTDIR)$(MAN1DIR) + +clean: + rm -f $(MANPAGES) diff --git a/doc/pipeline-design.md b/doc/pipeline-design.md new file mode 100644 index 0000000..f7e6df4 --- /dev/null +++ b/doc/pipeline-design.md @@ -0,0 +1,63 @@ +# Video Senders (Qubes RPC Services) + +## -q (quiet) +### gst-launch-1.0 needs to be quiet because the debug messages are printed on standard output instead of standard error +- This means the debug info will become part of the video output upon being sent to the `fdsink` element which is unwanted behavior that results in video artifacts + +## queue +### Force push mode scheduling which is better for a constant stream of data +- https://gstreamer.freedesktop.org/documentation/additional/design/scheduling.html + +## format=I420 +### This pixel format was chosen for two reasons +1. I420 seemed to be the default output format for a lot of elements and was always listed at the top of pixel format lists in the GStreamer documentation as well as over places such as on the official FOURCC website which defines these pixel formats + - So, presumably I420 is the most battle-hardened format which is least likely to contain bugs + - GStreamer documentation mentions to also prefer I420 over YV12 which are the same but with the V and U planes switched around +2. I420 is directly compatible with many GStreamer elements including `v4l2sink` without needing a leading `videoconvert` (probably followed by a `tee`) + - This greatly improves performance and security +- https://gstreamer.freedesktop.org/documentation/additional/design/mediatype-video-raw.html +- https://gstreamer.freedesktop.org/documentation/application-development/basics/elements.html +- https://www.fourcc.org/yuv.php + +## use-damage=false in `qvc.ScreenShare` +### XDamage causes `ximagesrc` to send out small updates about what parts of the screen has changed as opposed to just sending the whole screen +- This may be preferable on a network because of the decrease in bandwidth and latency but otherwise it just results in very high CPU usage and doesn't fit our use case + + +## BGRx -> I420 pixel format `videoconvert` in `qvc.ScreenShare` +### `ximagesrc` only outputs in BGRx so it must be converted to I420 which is a supported input format for `v4l2sink` (on the `receiver.sh` side) +- This video conversion is done on the side of the sending machine as to ensure the attack surface of the recipient stays as small as possible + +# Video Receiver (`receiver.sh`) + +## capsfilter +### This is used to limit our attack surface to the given capabilities +- All the capabilities after the colorimetry are technically unnecessary for this to be functional but are used to limit our attack surface +- This filter accounts for all the capabilities possible on a raw video stream according to the below documentation +- https://gstreamer.freedesktop.org/documentation/coreelements/capsfilter.html +- https://gstreamer.freedesktop.org/documentation/additional/design/mediatype-video-raw.html +- https://gstreamer.freedesktop.org/documentation/application-development/basics/pads.html#what-capabilities-are-used-for + +## colorimetry=2:4:7:1 +### This is the default colorimetry format for I420 and the only one that works with it without having to specify a `chroma-site` +- Having `chroma-site` set to `none` reduces our attack surface and likely improves our performance as well + +## use-sink-caps=true +### Use the capabilities defined by the previous element, in this case, what is explicitly defined by the capsfilter + +## sync=false +### Disable syncing video to the system clock +- This fixes the frame jittering/lagging that happens when passing raw video between machines +- `sync=false` is the default on the `fdsink` sink on the video sender + - Tried making both sender and receiver `sync=true` but that resulted in jittering again + - Also tried making only the `fdsink` sink on the sender `sync=true` and the `v4l2sink` sink on the receiver `sync=false` but that resulted in higher CPU usage and greater video latency +- Some times it takes some time to start jittering/lagging depending on what the video source is but it will happen +- I think it's because the two VMs are on slightly different clocks so trying to synchronize to that doesn't work out very well + - This fix isn't necessary when passing raw video directly between file descriptors on the same machine +- This may be better fixed by passing timestamp information through the raw video itself + - However, it appears to be that raw video is incapable of holding timestamp information + - Timestamping is done on raw video in the GStreamer pipeline but once it leaves there (through `fdsink`) any timestamping information may be void (unsure) + - Although, research has found a few somewhat hacky solutions for FFmpeg that allowed for timestamping on raw video; perhaps GStreamer has something similar +- Or maybe we could synchronize the clocks of the machines better thus allowing us to remove `sync=false` + - Or even just synchronize the clocks that the GStreamer processes see on each machine right before running them + - Perhaps using the `datefudge` or `faketime` command which uses `LD_PRELOAD` to manipulate the system time for a given command diff --git a/doc/qubes-video-companion.rst b/doc/qubes-video-companion.rst new file mode 100644 index 0000000..65cbcea --- /dev/null +++ b/doc/qubes-video-companion.rst @@ -0,0 +1,28 @@ +===================== +qubes-video-companion +===================== + +NAME +==== +qubes-video-companion - securely stream webcams and share screens across virtual machines + +SYNOPSIS +======== +| qubes-video-companion + +DESCRIPTION +=========== +Qubes Video Companion is a tool for securely streaming webcams and sharing screens across virtual machines. + +It accomplishes this by creating a uni-directional flow of raw video that is passed from one virtual machine to another through file descriptors thereby allowing both machines to be completely air-gapped with no networking stacks exposed. This design makes the side of the video sending virtual machine 100% immune to attack and only leaves a very small attack surface on the side of the video receiving virtual machine. + +The project emphasizes correctness and security all the while also sporting superb performance by maintaining a small footprint of the available computational resources and low latency even at Full HD and greater resolutions at 30 or more frames per second. + +OPTIONS +======= +video_source + The video source to stream and receive video from. Either "webcam" or "screenshare". + +AUTHORS +======= +| Elliot Killick diff --git a/doc/visualizations/qvc.ScreenShare.pdf b/doc/visualizations/qvc.ScreenShare.pdf new file mode 100644 index 0000000..acb3bdf Binary files /dev/null and b/doc/visualizations/qvc.ScreenShare.pdf differ diff --git a/doc/visualizations/qvc.Webcam.pdf b/doc/visualizations/qvc.Webcam.pdf new file mode 100644 index 0000000..0fe8079 Binary files /dev/null and b/doc/visualizations/qvc.Webcam.pdf differ diff --git a/doc/visualizations/receiver-qvc.ScreenShare.pdf b/doc/visualizations/receiver-qvc.ScreenShare.pdf new file mode 100644 index 0000000..03805a5 Binary files /dev/null and b/doc/visualizations/receiver-qvc.ScreenShare.pdf differ diff --git a/doc/visualizations/receiver-qvc.Webcam.pdf b/doc/visualizations/receiver-qvc.Webcam.pdf new file mode 100644 index 0000000..9fa87d9 Binary files /dev/null and b/doc/visualizations/receiver-qvc.Webcam.pdf differ diff --git a/icons/logo.png b/icons/logo.png new file mode 100644 index 0000000..e283bc3 Binary files /dev/null and b/icons/logo.png differ diff --git a/qubes-rpc/policies/qvc.ScreenShare b/qubes-rpc/policies/qvc.ScreenShare new file mode 100644 index 0000000..9146dc6 --- /dev/null +++ b/qubes-rpc/policies/qvc.ScreenShare @@ -0,0 +1,3 @@ +@anyvm @dispvm allow +@anyvm @anyvm ask +@anyvm dom0 ask diff --git a/qubes-rpc/policies/qvc.Webcam b/qubes-rpc/policies/qvc.Webcam new file mode 100644 index 0000000..1074290 --- /dev/null +++ b/qubes-rpc/policies/qvc.Webcam @@ -0,0 +1,2 @@ +@anyvm sys-usb ask +@anyvm dom0 ask diff --git a/qubes-rpc/services/qvc.ScreenShare b/qubes-rpc/services/qvc.ScreenShare new file mode 100755 index 0000000..6044515 --- /dev/null +++ b/qubes-rpc/services/qvc.ScreenShare @@ -0,0 +1,34 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +export DISPLAY=":0" + +first_connected_screen_input_dimensions="$(xrandr | grep -w "connected" | head -1 | cut -d '+' -f 1 | awk '{ print $NF }')" + +width="$(echo "$first_connected_screen_input_dimensions" | awk -F x '{ print $1 }')" +height="$(echo "$first_connected_screen_input_dimensions" | awk -F x '{ print $2 }')" + +fps="30" +frame_rate="$fps/1" + +echo "$width $height $fps" + +echo "Starting screen sharing at ${width}x${height} ${fps} FPS..." >&2 + +source "/usr/share/qubes-video-companion/ui/ui.sh" +trap 'remove_ui $!' EXIT +create_ui screenshare + +gst-launch-1.0 -q ximagesrc use-damage=false ! \ + queue ! \ + "video/x-raw,width=$width,height=$height,framerate=$frame_rate,format=BGRx" ! \ + videoconvert ! \ + "video/x-raw,format=I420" ! \ + fdsink diff --git a/qubes-rpc/services/qvc.Webcam b/qubes-rpc/services/qvc.Webcam new file mode 100755 index 0000000..453331c --- /dev/null +++ b/qubes-rpc/services/qvc.Webcam @@ -0,0 +1,33 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +webcam_device="/dev/video0" +# This command exits with code 255 even though no error is reported +v4l2_webcam_info="$(v4l2-ctl -d "$webcam_device" --all)" || [ $? == 255 ] + +dimensions="$(echo "$v4l2_webcam_info" | grep 'Width/Height' | awk '{ print $3 }')" +width="$(echo "$dimensions" | awk -F / '{ print $1 }')" +height="$(echo "$dimensions" | awk -F / '{ print $2 }')" + +frame_rate="$(v4l2-ctl -d "$webcam_device" --all | grep 'Frames per second' | awk '{ print $5 }' | tr -d '()')" +fps="$(echo "$frame_rate" | awk -F / '{ print $1 }')" # Support a minimum of one frame per second + +echo "$width $height $fps" + +echo "Starting webcam stream at ${width}x${height} ${fps} FPS..." >&2 + +source "/usr/share/qubes-video-companion/ui/ui.sh" +trap 'remove_ui $!' EXIT +create_ui webcam + +gst-launch-1.0 -q v4l2src ! \ + queue ! \ + "video/x-raw,width=$width,height=$height,framerate=$frame_rate,format=I420" ! \ + fdsink diff --git a/rpm_spec/qubes-video-companion-dom0.spec.in b/rpm_spec/qubes-video-companion-dom0.spec.in new file mode 100644 index 0000000..b1974a0 --- /dev/null +++ b/rpm_spec/qubes-video-companion-dom0.spec.in @@ -0,0 +1,69 @@ +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +%define vm_name qubes-video-companion + +Name: %{vm_name}-dom0 +Version: 1.0.0 +Release: 1%{?dist} +Summary: Securely stream webcams and share screens across virtual machines + +License: MIT +URL: https://github.com/elliotkillick/qubes-video-companion +Source0: %{vm_name}-%{version}.tar.gz + +BuildArch: noarch + +Requires: gstreamer1-plugins-good + +%description +Qubes Video Companion is a tool for securely streaming webcams and sharing +screens across virtual machines. + +It accomplishes this by creating a uni-directional flow of raw video that is +passed from one virtual machine to another through file descriptors thereby +allowing both machines to be completely air-gapped with no networking stacks +exposed. This design makes the side of the video sending virtual machine 100% +immune to attack and only leaves a very small attack surface on the side of +the video receiving virtual machine. + +The project emphasizes correctness and security all the while also sporting +superb performance by maintaining a small footprint of the available +computational resources and low latency even at Full HD and greater +resolutions at 30 or more frames per second. + +This package contains only the video senders, Qubes RPC policies and +associated documentation components of Qubes Video Companion which are +necessary for dom0. + +%prep +%autosetup -n %{vm_name}-%{version} + +%build +# Enable Python 3 for Qubes R4.0 dom0 (Fedora 25) support because it defaults to Python 2 +%define __python %{__pythonX} +%make_build + +%install +rm -rf $RPM_BUILD_ROOT +make DESTDIR=%{?buildroot} install-dom0 install-license + +%files +%{_licensedir}/qubes-video-companion/LICENSE +%{_docdir}/qubes-video-companion/README.md +%{_docdir}/qubes-video-companion/pipeline-design.md +%{_docdir}/qubes-video-companion/visualizations/* +%{_sysconfdir}/qubes-rpc/qvc.Webcam +%{_sysconfdir}/qubes-rpc/qvc.ScreenShare +%{_sysconfdir}/qubes-rpc/policy/qvc.Webcam +%{_sysconfdir}/qubes-rpc/policy/qvc.ScreenShare +%{_datadir}/qubes-video-companion/ui/ui.sh +%{_datadir}/qubes-video-companion/ui/main.py +%{_datadir}/qubes-video-companion/ui/user_interface.py +%{_datadir}/qubes-video-companion/ui/notification.py +%{_datadir}/qubes-video-companion/ui/tray_icon.py +%{_datadir}/qubes-video-companion/scripts/set-webcam-format.sh + +%changelog +* Wed Feb 17 2021 Elliot Killick 1.0.0-1 +- Initial release of the package diff --git a/rpm_spec/qubes-video-companion.spec.in b/rpm_spec/qubes-video-companion.spec.in new file mode 100644 index 0000000..22ba5f4 --- /dev/null +++ b/rpm_spec/qubes-video-companion.spec.in @@ -0,0 +1,72 @@ +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +Name: qubes-video-companion +Version: 1.0.0 +Release: 1%{?dist} +Summary: Securely stream webcams and share screens across virtual machines + +License: MIT +URL: https://github.com/elliotkillick/qubes-video-companion +Source0: %{name}-%{version}.tar.gz + +BuildArch: noarch + +BuildRequires: pandoc +Requires: gstreamer1-plugins-good + +%description +Qubes Video Companion is a tool for securely streaming webcams and sharing +screens across virtual machines. + +It accomplishes this by creating a uni-directional flow of raw video that is +passed from one virtual machine to another through file descriptors thereby +allowing both machines to be completely air-gapped with no networking stacks +exposed. This design makes the side of the video sending virtual machine 100% +immune to attack and only leaves a very small attack surface on the side of +the video receiving virtual machine. + +The project emphasizes correctness and security all the while also sporting +superb performance by maintaining a small footprint of the available +computational resources and low latency even at Full HD and greater +resolutions at 30 or more frames per second. + +This package contains all components of Qubes Video companion excluding the +Qubes RPC policies which dom0 enforces. + +%prep +%autosetup + +%build +%make_build + +%install +rm -rf $RPM_BUILD_ROOT +make DESTDIR=%{?buildroot} install-vm install-license + +%files +%{_licensedir}/qubes-video-companion/LICENSE +%{_docdir}/qubes-video-companion/README.md +%{_docdir}/qubes-video-companion/pipeline-design.md +%{_docdir}/qubes-video-companion/visualizations/* +%{_mandir}/man1/qubes-video-companion.1.gz +%{_bindir}/qubes-video-companion +%{_sysconfdir}/qubes-rpc/qvc.Webcam +%{_sysconfdir}/qubes-rpc/qvc.ScreenShare +%{_datadir}/qubes-video-companion/video/setup.sh +%{_datadir}/qubes-video-companion/video/receiver.sh +%{_datadir}/qubes-video-companion/video/destroy.sh +%{_datadir}/qubes-video-companion/video/common.sh +%{_datadir}/qubes-video-companion/ui/ui.sh +%{_datadir}/qubes-video-companion/ui/main.py +%{_datadir}/qubes-video-companion/ui/user_interface.py +%{_datadir}/qubes-video-companion/ui/notification.py +%{_datadir}/qubes-video-companion/ui/tray_icon.py +%{_datadir}/qubes-video-companion/scripts/set-webcam-format.sh +%{_datadir}/qubes-video-companion/scripts/webcam.html +%{_datadir}/qubes-video-companion/scripts/v4l2loopback/install.sh +%{_datadir}/qubes-video-companion/scripts/v4l2loopback/author.asc + +%changelog +* Wed Feb 17 2021 Elliot Killick 1.0.0-1 +- Initial release of the package diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 0000000..b18ccb4 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1 @@ +visualizations diff --git a/scripts/generate-visualizations.sh b/scripts/generate-visualizations.sh new file mode 100755 index 0000000..103d7ab --- /dev/null +++ b/scripts/generate-visualizations.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +# Generate visualizations from gst-launch-1.0 command outputted DOT files + +# Only the PAUSED -> PLAYING edge GStreamer visualizations are kept in doc/visualizations because including all of them would result in a lot of redundant information and be overly verbose +# The PAUSED -> PLAYING edge was picked for display in the documentation because it shows what the GStreamer pipeline looks like when it is running + +# Create DOT files by putting this in the video sender (Qubes RPC service) and receiver scripts: +# Get visualizations (Must create directory) +#export GST_DEBUG_DUMP_DOT_DIR="$HOME/visualizations" + +# This script is not installed, it's just used in development for making the visualizations + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +local_dir="$(dirname "$(readlink -f "$0")")" +doc_visualizations_dir="$(readlink -f "$local_dir/../doc/visualizations")" + +cd "$local_dir/visualizations" || exit + +for visualization in *; do + # Filter out non-files and workaround shell issue of no files being present leading to the literal interprataion of * as a filename + if [ -f "$visualization" ]; then + dot -Tpdf "$visualization" > "$doc_visualizations_dir/${visualization%.*}.pdf" + fi +done + +rm -f ./* diff --git a/scripts/set-webcam-format.sh b/scripts/set-webcam-format.sh new file mode 100755 index 0000000..39fc29d --- /dev/null +++ b/scripts/set-webcam-format.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +# Run this in the VM with the webcam device attached + +# Fix V4L2 webcam settings if it changes +# This changed the resolution to 1920x1080 which is the highest my webcam can do +# - How to find the highest resolution a webcam is capable of? +# This also sets the webcam output format to MJPG which is the most widely supported and best performing one (in terms of latency) +# Why do these settings change by themselves sometimes? +# - It happens sometimes upon plugging in and out my webcam, restarting my computer, reloading V4l2 related kernel modules etc. +# More research needed + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +webcam_device="/dev/video0" + +print_all_webcam_info() { + v4l2-ctl -d "$webcam_device" --all || [ $? == 255 ] # Ignore 255 error code because this command returns 255 even though no error is reported for an unknown reason (research required) +} + +echo "Previous V4L2 device information:" >&2 +print_all_webcam_info + +# Replace width and height with the highest dimensions your webcam is capable of outputting +v4l2-ctl -d "$webcam_device" --set-fmt-video=pixelformat=MJPG,width=1920,height=1080 + +echo -e "\nNew V4L2 device information:" >&2 +print_all_webcam_info diff --git a/scripts/v4l2loopback/author.asc b/scripts/v4l2loopback/author.asc new file mode 100644 index 0000000..f45e26f --- /dev/null +++ b/scripts/v4l2loopback/author.asc @@ -0,0 +1,960 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +UjvcOMV9Qmp6CevrbuRMtENJT2hhbm5lcyBtIHptw7ZsbmlnIChmb3J1bTo6ZsO8 +cjo6dW1sw6R1dGUpIDxmb3J1bUB1bWxhZXV0ZS5tdXIuYXQ+iQJUBBMBCAA+AhsD +BQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAFiEEdAXnRVdICXNIABVttlAZxH96NvgF +Al3wEtsFCQ3atlAACgkQtlAZxH96NviQeg//b0b/VCj6RcqcOrkzj3dHc+WKu2rg +9UPx31NnEAEF7sNd7uE9NYETpUtAZmz1FfAid9KEM3e/L177UxPBBn/vLG3AmsWL +Z/sRJpdB4PRKSzexA6dRQ++W94FcRwjp76Qpj3T5yfivGeAh0XCleC6WawnAtDBY +jXBD/KLRSjRXGrER9hNlYtrNvJXOVuBMI4q1ialUJsq1Go3oDInyTkujdBlGw1ZU +18Ekv3NfQKHftt8fph0M7bKITNqmzBElBqx1F2fRH2FO0TXKG1D4rGyZa6/ePdkb +dDzZ1uDU/XCaAqT6AqO84lcfF9/6udp9Q2YDjwRYZPkT3CScns8STeKYAvZOYlCZ +vmSG5qT1ngNNRH//5/QzqwrgVixQ1O9pGRa1XBHqGsLVjGm4Isu/lbxj1qXYRbyN +QPK2MznEV9/pe1hKgY4aj1vNpHE4VZ7FtneVUsN803RzSlpyndN+wrW0RZSCmA4z +K/1Gde4ms6nsVFA7hFmkz7Bp/lHrV3/HOL3u1OutmLaMzc1nhEDWuzSlsaFyl+83 +WZDacIclMy1jjBt6QdX0WJlGvq7fDoqvJ+B5bLwVy1z5xLjC91GN0sWuIR3l4X48 +ORWsRXkdLmTOUfE1K4MvnPmTfH7AmMqLJjYyC6RJwo0PIOv93jWpJj9EXtcw7zgC +G+1CNz+HNnBUX6qJAlQEEwEIAD4CGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AW +IQR0BedFV0gJc0gAFW22UBnEf3o2+AUCXFblegUJC/kGbwAKCRC2UBnEf3o2+CbH +EACN/rZh+X11Hnts6Vq6pNfJpjHtye74eLUNnReR2GmR+36o3VSil4P5bsGs5a7d +XnNz2ybaOI0Dzbj1MKfrqBaxNlYxSmn+Y/1FKQyWDuQBeuXy5h+2nzF5FJkIddGK +FIW/+cMs5WQn2flWCyoPFbwkFYKn7Tua0WCy9GXmL1+Ny9kDOp1BkC2QnELS5Xhh +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 +r5KwjALHfYdK3kRso/51to6KgX/fJdHUVNRSARAAAQEAAAAAAAAAAAAAAAD/2P/g +ABBKRklGAAEBAQBIAEgAAP/bAEMAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB +AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/bAEMBAQEBAQEB +AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB +AQEBAQEBAQEBAf/AABEIAI4AagMBIgACEQEDEQH/xAAfAAAABgMBAQEAAAAAAAAA +AAAABAUICQoGBwsCAQP/xABaEAAABAEHBwcGCQYKCQUAAAABBAUGEQAHCBQVITEC +A0FRYXHwCRYkJTSBoRM1kbHB0QoSJkRFVFXh8RciI1JkhTIzNkJWZXSCkuIYGUZT +dZSVotJjc8LV9f/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAA +AAAAAAAA/9oADAMBAAIRAxEAPwC/Ha5Xb4+6Qtcrt8fdLEKzt4/wy/SQZXa5Xb4+ +6RQwsF6v0YccNmzbt0yx+QkGQWxxD/LIWxmdXrloeeGeibeYGb9YnSnRcic0ma3u +2LB/xuvlS25Qj4TO+Ft0G5uKIRJNbzDC1SKu8F+FpqiaIDkx3wEQEbhgIhhiF6bP +vVELGqgYV06uCHZBVUgBvwjG8NwhvhhI9lL2eyg/QZOTlYYgA6NvG7GXNcmn5cim +QiJ/SXhMy9yptR7G7Zq2emCO9YR+uI8aJTGUb+XgfDbMEjM7TDbptBN9sKMMLTH/ +AJPnEdkFyS2OIf5ZC2OIf5ZMDo/0+KPFJBPJmWA8KoqGwSq4215KstTSuNOuG6T3 +JAqGVgxdVguiPGzZvxxkDCxnww8LtWrf69ckuQkCraJjb/i/zS82se/VD0fdJMki +eXMfqqPpkC3ISEhIBLWc586DHmcZ5x7P9yJzeQSmJw+q3KuHY46O/WMswcDgR22j +nFtbOpySlpKdXzhw/f1YmB7REPuhKj3yuFOBPpITwOVpJs5Ci05m5vfPFQC03M6o +9jZ6P/xi8NQ7JAw/lqeVocFKudj8m80qOouFmt7oCPX1X5M2n9r2RHRfjj6q67wm +/nQLFzjtciOom6321YIdaJmICPYvZfKyBRf5LctPGXWHbOOT5vc4U5VsdHIec0pN +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 +=CKdU +-----END PGP PUBLIC KEY BLOCK----- diff --git a/scripts/v4l2loopback/install.sh b/scripts/v4l2loopback/install.sh new file mode 100755 index 0000000..f69d814 --- /dev/null +++ b/scripts/v4l2loopback/install.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +# Fedora requires this due to there being no v4l2loopback package because it's a kernel module +# https://fedoraproject.org/wiki/Package_maintainers_wishlist?rd=PackageMaintainers/WishList#T-W +# https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/XPNXY6RYUPBDF5STIBQVLGRWNJ6AYKO7/#DWPK323DNTKDH7SDPQGNDSKBIEQKRKN4 + +# Debian (stable) also requires this because the package provided is very out-of-date and doesn't even install correctly (whereas later versions do) + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +local_dir="$(dirname "$(readlink -f "$0")")" + +# Package dependencies installed by package manager + +# Author PGP key initial source +#gpg --keyserver keyring.debian.org --recv-keys 7405E745574809734800156DB65019C47F7A36F8 + +gpg --import "$local_dir/author.asc" + +cd /tmp || exit +git clone https://github.com/umlaeute/v4l2loopback +cd v4l2loopback || exit + +latest_version_tag="$(git describe --abbrev=0)" +if ! git verify-tag "$latest_version_tag"; then + echo "Failed to verify v4l2loopback author PGP key!" >&2 + exit 1 +fi +git checkout "$latest_version_tag" + +make +sudo make install + +cd .. || exit +rm -rf v4l2loopback diff --git a/scripts/webcam.html b/scripts/webcam.html new file mode 100644 index 0000000..d3b0d86 --- /dev/null +++ b/scripts/webcam.html @@ -0,0 +1,30 @@ + + + + + + + Video Stream + + + + + + diff --git a/ui/main.py b/ui/main.py new file mode 100755 index 0000000..3c109b9 --- /dev/null +++ b/ui/main.py @@ -0,0 +1,48 @@ +#!/usr/bin/python3 + +"""Display user interface elements""" + +import argparse +import notification +import tray_icon + +def main(): + """Program entry point""" + + args = parse_args() + display_notification(args.video_source, args.requested_target, args.remote_domain) + display_tray_icon(args.video_source, args.requested_target, args.remote_domain) + +def parse_args(): + """Parse command-line arguments""" + + parser = argparse.ArgumentParser() + parser.add_argument('-s', '--video-source', type=str, required=True, + help='Video stream source') + parser.add_argument('-t', '--requested-target', type=str, required=True, + help='Qrexec requested target') + parser.add_argument('-r', '--remote-domain', type=str, required=True, + help='Qrexec remote domain') + return parser.parse_args() + +def display_notification(video_source, requested_target, remote_domain): + """Show notification to the user""" + + notification_ui = notification.Notification() + notification_ui.video_source = video_source + notification_ui.requested_target = requested_target + notification_ui.remote_domain = remote_domain + notification_ui.show() + +def display_tray_icon(video_source, requested_target, remote_domain): + """Show system tray icon to the user""" + + tray_icon_ui = tray_icon.TrayIcon() + tray_icon_ui.video_source = video_source + tray_icon_ui.requested_target = requested_target + tray_icon_ui.remote_domain = remote_domain + tray_icon_ui.create() + tray_icon_ui.show() + +if __name__ == '__main__': + main() diff --git a/ui/notification.py b/ui/notification.py new file mode 100755 index 0000000..cb25758 --- /dev/null +++ b/ui/notification.py @@ -0,0 +1,30 @@ +#!/usr/bin/python3 + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +"""Notification user interface module""" + +# GI requires version declaration before importing +# pylint: disable=wrong-import-position + +import gi +gi.require_version('Notify', '0.7') +from gi.repository import Notify + +import user_interface + +class Notification(user_interface.UserInterface): + """Notification user interface component""" + + def __init__(self): + Notify.init(self.app) + + def show(self): + """Display notification to the user""" + + Notify.Notification.new(self.app, self.build_message(), self.video_source_to_icon()).show() + + @staticmethod + def __destroy__(): + Notify.uninit() diff --git a/ui/tray_icon.py b/ui/tray_icon.py new file mode 100755 index 0000000..f79afb9 --- /dev/null +++ b/ui/tray_icon.py @@ -0,0 +1,80 @@ +#!/usr/bin/python3 + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +"""Tray icon user interface module""" + +# Use AppIndicator3 because GTK deprecated GtkStatusIcon +# https://stackoverflow.com/questions/41917903/gtk-3-statusicon-replacement + +# GI requires version declaration before importing +# pylint: disable=wrong-import-position + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Prefer AyatanaAppIndicator becuase it's under active development +# This is opposed to AppIndicator which is abandonware +# Fallback on old AppIndicator for Qubes R4.0 dom0 which uses Fedora 25 +try: + gi.require_version('AyatanaAppIndicator3', '0.1') + from gi.repository import AyatanaAppIndicator3 as AppIndicator +except (ImportError, ValueError): + gi.require_version('AppIndicator3', '0.1') + from gi.repository import AppIndicator3 as AppIndicator + +import user_interface + +class TrayIcon(user_interface.UserInterface): + """Tray icon user interface component""" + + def __init__(self): + self.indicator = None + + def create(self): + """Create tray icon""" + + category = AppIndicator.IndicatorCategory.APPLICATION_STATUS + self.indicator = AppIndicator.Indicator.new(self.app, self.video_source_to_icon(), category) + self.indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE) + self.indicator.set_menu(self.menu()) + + def menu(self): + """Create tray icon menu""" + + menu = Gtk.Menu() + + header = Gtk.MenuItem.new_with_label(self.app) + label = Gtk.MenuItem.get_child(header) + label.set_markup('' + label.get_text() + '') + menu.append(header) + + entry = Gtk.MenuItem.new_with_label(self.build_message()) + menu.append(entry) + menu.show_all() + + return menu + + @staticmethod + def show(): + """Show tray icon""" + + # pylint: disable=line-too-long + # FIXME: The following warning appears: Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed + # This seems to be an issue in AppIndicator that others are also experiencing + + # The fix seems to be here but implementing that locally would probably + # be difficult without overriding AppIndicator methods to patch it: + # https://github.com/dorkbox/SystemTray/issues/19 + + # Create an issue or make a pull request on the Ayatana version + # of AppIndicator that's currently being maintained to fix this + Gtk.main() + + @staticmethod + def close(): + """Remove tray icon""" + + Gtk.main_quit() diff --git a/ui/ui.sh b/ui/ui.sh new file mode 100755 index 0000000..6b873d2 --- /dev/null +++ b/ui/ui.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +create_ui() { + video_sender="$1" + + DISPLAY=:0 /usr/share/qubes-video-companion/ui/main.py -s "$video_sender" -t "$(hostname)" -r "$QREXEC_REMOTE_DOMAIN" &> /dev/null & +} + +remove_ui() { + pid="$1" + + if [ "$pid" ]; then + kill "$pid" + fi +} diff --git a/ui/user_interface.py b/ui/user_interface.py new file mode 100755 index 0000000..e6a9fed --- /dev/null +++ b/ui/user_interface.py @@ -0,0 +1,34 @@ +#!/usr/bin/python3 + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +"""User interface base module""" + +import sys + +class UserInterface: + """User interface base class""" + + app = "Qubes Video Companion" + video_source = "" + requested_target = "" + remote_domain = "" + + def build_message(self): + """Create video stream message for being displayed to the user""" + + return self.video_source + ": " + self.requested_target + " -> " + self.remote_domain + + def video_source_to_icon(self): + """Convert video source to icon name""" + + conversion_dict = { + 'webcam': 'camera-web', + 'screenshare': 'video-display' + } + try: + return conversion_dict[self.video_source] + except KeyError: + print('Video source does not exist:', self.video_source, file=sys.stderr) + sys.exit(1) diff --git a/video/common.sh b/video/common.sh new file mode 100755 index 0000000..baa0e69 --- /dev/null +++ b/video/common.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +# Test if v4l2loopback kernel module is installed +test_v4l2loopback() { + # Prepend sbin directories to PATH because some distributions don't include it in the user profile by default + PATH="/usr/local/sbin:/usr/sbin:/sbin:$PATH" modinfo v4l2loopback &> /dev/null +} diff --git a/video/destroy.sh b/video/destroy.sh new file mode 100755 index 0000000..9c18cc1 --- /dev/null +++ b/video/destroy.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +source /usr/share/qubes-video-companion/video/common.sh + +if ! test_v4l2loopback; then + exit 1 +fi + +# "videodev" is the Video4Linux (V4L) driver (V4L2 is the second version of V4L) +until sudo modprobe -r v4l2loopback videodev; do + message="Please close any window that has an open video stream so kernel modules can be securely unloaded..." + echo "$message" >&2 + notify-send "Qubes Video Companion" "$message" + + sleep 10 +done diff --git a/video/qubes-video-companion b/video/qubes-video-companion new file mode 100755 index 0000000..a0b1797 --- /dev/null +++ b/video/qubes-video-companion @@ -0,0 +1,57 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +usage() { + echo "Usage: qubes-video-companion webcam|screenshare" +} >&2 + +video_source="$1" + +case "$video_source" in + webcam) + qvc_service="qvc.Webcam" + ;; + screenshare) + qvc_service="qvc.ScreenShare" + ;; + *) + usage + exit 1 + ;; +esac + +exit_clean() { + exit_code="$?" + + /usr/share/qubes-video-companion/video/destroy.sh + sudo rm -f "$qvc_lock_file" + + if [ "$video_source" == "webcam" ] && [ "$exit_code" == "141" ]; then + echo "The webcam device is in use! Please stop any instance of Qubes Video Companion running on another qube." >&2 + exit 1 + fi + + exit "$exit_code" +} + +qvc_lock_file="/run/lock/qubes-video-companion" +if ! [ -f "$qvc_lock_file" ]; then + trap exit_clean EXIT + sudo touch "$qvc_lock_file" +else + echo "Qubes Video Companion is already running! Please stop the previous session before starting a new one." >&2 + exit 1 +fi + +/usr/share/qubes-video-companion/video/setup.sh +# Disabling buffering should lower latency and it doesn't seem to impact performance +# Filter standard error escape characters for safe printing to the terminal from the video sender +cd /usr/share/qubes-video-companion/video || exit +qrexec-client-vm dom0 "$qvc_service" /usr/share/qubes-video-companion/video/receiver.sh --buffer-size=0 --filter-escape-chars-stderr diff --git a/video/receiver.sh b/video/receiver.sh new file mode 100755 index 0000000..51fb733 --- /dev/null +++ b/video/receiver.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +read -r untrusted_width untrusted_height untrusted_fps + +# Prevent injection of any additional capabilities to the GStreamer pipeline +# This also removes terminal escape sequences for printing to the terminal +sanitize_cap() { + local cap_value="$1" + + # Allow only numbers + echo "$cap_value" | tr -cd 0-9 +} + +width="$(sanitize_cap "$untrusted_width")" +height="$(sanitize_cap "$untrusted_height")" +fps="$(sanitize_cap "$untrusted_fps")" + +if ! { [ "$width" ] || [ "$height" ] || [ "$fps" ]; }; then + echo -e "Video sender failed to provide necessary parameters for video stream creation! Exiting..." >&2 + exit 1 +fi + +echo "Receiving video stream at ${width}x${height} ${fps} FPS..." >&2 + +frame_rate="$fps/1" + +gst-launch-1.0 fdsrc ! \ + queue ! \ + capsfilter caps="video/x-raw,width=$width,height=$height,framerate=$frame_rate,format=I420,colorimetry=2:4:7:1,chroma-site=none,interlace-mode=progressive,pixel-aspect-ratio=1/1,max-framerate=$frame_rate,views=1" ! \ + rawvideoparse use-sink-caps=true ! \ + v4l2sink device=/dev/video0 sync=false diff --git a/video/setup.sh b/video/setup.sh new file mode 100755 index 0000000..f553cd2 --- /dev/null +++ b/video/setup.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Copyright (C) 2021 Elliot Killick +# Licensed under the MIT License. See LICENSE file for details. + +[ "$DEBUG" == 1 ] && set -x + +set -E # Enable function inheritance of traps +trap exit ERR + +source /usr/share/qubes-video-companion/video/common.sh + +if ! test_v4l2loopback; then + echo "The v4l2loopback kernel module is not installed. Please run the following script to install it: /usr/share/qubes-video-companion/scripts/v4l2loopback/install.sh" + exit 1 +fi + +# exclusive_caps=1: Some applications such as Cheese and Chromium won't detect the video device if it's set to zero +sudo modprobe v4l2loopback card_label="Qubes Video Companion" exclusive_caps=1 + +# For some reason, AppVMs based off my self-made "kali" qube (which itself is based off the "debian-10" TemplateVM) that are using the 5.x Qubes Linux kernel no longer hasthe user permitting ACL (or any ACL for that matter) on /dev/video* devices causing a permission error when attempting to write video to the device +# As a workaround, we set the ACL ourselves in case it isn't already applied +# This issue does not occur on the Fedora or Debian AppVMs using the 5.x Qubes Linux kernel, more research is required +sudo setfacl -m user:user:rw /dev/video0