Skip to content
This repository has been archived by the owner on Jun 24, 2023. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ElliotKillick committed Apr 7, 2021
0 parents commit 314b1ee
Show file tree
Hide file tree
Showing 41 changed files with 2,207 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.swp
*.gz
__pycache__
23 changes: 23 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (C) 2021 Elliot Killick <elliotkillick@zohomail.eu>
# 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'
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (C) 2021 Elliot Killick <elliotkillick@zohomail.eu>

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.
60 changes: 60 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright (C) 2021 Elliot Killick <elliotkillick@zohomail.eu>
# 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
213 changes: 213 additions & 0 deletions README.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions ci/requirements.txt
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
qubes-video-companion (1.0.0-1) unstable; urgency=low

* Initial release

-- Elliot Killick <elliotkillick@zohomail.eu> Thu, 18 Feb 2021 11:40:26 -0500
34 changes: 34 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Source: qubes-video-companion
Section: video
Priority: optional
Maintainer: Elliot Killick <elliotkillick@zohomail.eu>
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.
29 changes: 29 additions & 0 deletions debian/copyright
Original file line number Diff line number Diff line change
@@ -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 <elliotkillick@zohomail.eu>
Source: https://github.com/elliotkillick/qubes-video-companion

Files: *
Copyright: 2021 Elliot Killick <elliotkillick@zohomail.eu>
License: MIT
MIT License
.
Copyright (C) 2021 Elliot Killick <elliotkillick@zohomail.eu>
.
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.
7 changes: 7 additions & 0 deletions debian/rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/make -f

%:
dh $@

override_dh_auto_install:
make install-vm DESTDIR=$(shell readlink -f .)/debian/qubes-video-companion
1 change: 1 addition & 0 deletions debian/source/format
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.0 (quilt)
30 changes: 30 additions & 0 deletions doc/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright (C) 2021 Elliot Killick <elliotkillick@zohomail.eu>
# 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)
63 changes: 63 additions & 0 deletions doc/pipeline-design.md
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions doc/qubes-video-companion.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=====================
qubes-video-companion
=====================

NAME
====
qubes-video-companion - securely stream webcams and share screens across virtual machines

SYNOPSIS
========
| qubes-video-companion <video_source>
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 <elliotkillick at zohomail dot eu>
Binary file added doc/visualizations/qvc.ScreenShare.pdf
Binary file not shown.
Binary file added doc/visualizations/qvc.Webcam.pdf
Binary file not shown.
Binary file not shown.
Binary file added doc/visualizations/receiver-qvc.Webcam.pdf
Binary file not shown.
Binary file added icons/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions qubes-rpc/policies/qvc.ScreenShare
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@anyvm @dispvm allow
@anyvm @anyvm ask
@anyvm dom0 ask
2 changes: 2 additions & 0 deletions qubes-rpc/policies/qvc.Webcam
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@anyvm sys-usb ask
@anyvm dom0 ask
34 changes: 34 additions & 0 deletions qubes-rpc/services/qvc.ScreenShare
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash

# Copyright (C) 2021 Elliot Killick <elliotkillick@zohomail.eu>
# 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
33 changes: 33 additions & 0 deletions qubes-rpc/services/qvc.Webcam
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash

# Copyright (C) 2021 Elliot Killick <elliotkillick@zohomail.eu>
# 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
Loading

0 comments on commit 314b1ee

Please sign in to comment.