Basic GUI für magic-wormhole in AppleScript

Mit dem Tool magic-wormhole können Dateien zwischen zwei Computern verschlüsselt ausgetauscht werden. Mittels einem "Mailbox Server" (oder Relay Server) bauen beide Computer eine direkte Verbindung zueinander auf.
Somit wird für den Austausch von Dateien oder Ordnern kein Cloud Dienst (wie z.B. OneDrive, Dropbox, etc.) benötigt und die Dateien werden nicht auf einem Server zentral (zwischen-)gespeichert.

Weitere Informationen zum Tool und Entwickler:
https://github.com/magic-wormhole/magic-wormhole
https://magic-wormhole.readthedocs.io/en/latest/

Da das Tool standardmäßig nur ein CLI (command-line interface, Befehlszeile, auch bekannt als Eingabeaufforderung) besitzt und damit für wenig erfahrene Nutzer nur schwer zugänglich ist, wird in diesem Blogbeitrag eine simple GUI (grafische Benutzeroberfläche) vorgestellt. So wird auch dieser Benutzergruppe die Möglichkeit gegeben, von den Vorteilen des Tools zu profitieren.
Bei der hier vorgestellten GUI handelt es sich um einfache Dialogfelder, welche in AppleScript realisiert wurden.

magic-wormhole CLI (Befehlszeilen Interface)
magic-wormhole CLI (Befehlszeilen Interface)

Kurzübersicht

Dieser Blogbeitrag fokussiert auf folgende Themen:

  • Packen (mittels pyinstaller) und signieren von magic-wormhole (eine CLI Anwendung geschrieben in Python) auf macOS
  • Erstellen einer AppleScript-App, welche die o.g. CLI Executable ausführt
  • Archivieren beider Anwendungen in einem signierten .dmg
wormhole CLI und AppleScript App in einem signierten .dmg
wormhole CLI und AppleScript App in einem signierten .dmg

Themen

Motivation

Wie eingangs schon erwähnt, können mit magic-wormhole Daten sicher und verschlüsselt übertragen werden. Die Dateien werden nicht – wie bei einem Cloud oder Filesharing Dienst – zentral auf einem Server gespeichert und man schickt keine kryptischen Links / URLs hin und her, sondern beide Partner finden sich über sog. für Menschen gut lesbare, "sprechende" wormhole Codes. (siehe Funktionsweise)

Use Case

Das Tool bietet sich u.A. für Support Sessions an, wenn z.B. Anwendungen, Logfiles oder Datenbank Dumps zwischen einem Arbeitsplatz-PC und der IT Abteilung ausgetauscht werden müssen. Nicht immer hat man jedoch direkten Zugriff auf den Arbeitsplatz-PC (z.B. via VNC oder RDP), sodass man auf die Mithilfe des PC-Benutzers angewiesen ist. Dieser ist aber nicht immer (oder eher selten) in der Lage ein kommandozeilen-basiertes Tool zu bedienen. Für diesen Use Case ist eine GUI sinnvoll.

Technische Motivation (aus Entwicklersicht)

Die Motivation für dieses Projekt geht letztlich – aus Entwicklersicht – über die unter Use Case beschriebenen Themen hinaus. Folgende Aspekte spielen aus Gesichtspunkten der Softwareentwicklung eine entscheidende Rolle und werden in diesem Beitrag diskutiert:

Python CLI Anwendung packen und signieren (Code Signing auf macOS)

Um aus dem Quellcode für magic-wormhole ein Binary zu erstellen, das ohne Python Interpreter auskommt und somit ohne Abhängigkeiten direkt auf dem System ausgeführt werden kann, muss das Tool in ein standalone Executable gepackt werden. Dies erfolgt hierbei mit pyinstaller. Das Packen ist aus Entwicklersicht insofern "tricky", da macOS nur signierte Binaries ausführt.

Das führt zu der Fragestellung, wie mit pyinstaller auf macOS Binaries signiert werden, welche Zertifikate bzw. Keys dafür erforderlich sind und woher man diese bekommt.

CLI Executable aus AppleScript-App heraus ausführen

Eine weitere Aufgabe ergibt sich aus der Anforderung eine GUI zu erstellen, die hier in AppleScript realisiert wird. Dies führt zu der Fragestellung, wie eine CLI Executable aus einer AppleScript-App heraus ausgeführt werden kann.
Wie wird das realisiert, gerade unter dem Gesichtspunkt von relativen bzw. absoluten Pfadangaben zur Executable? Wie verhält es sich mit der Signierung der AppleScript App, welches Zertifikat bzw. welcher Key kann dafür genutzt werden? Entstehen dadurch bestimmte Abhängigkeiten zwischen AppleScript-App und CLI Executable?

Geeignete Distribution (von Executable und AppleScript-App)

Nach dem Packen und Signieren einer Anwendung stellt sich als nächstes die Frage, nach einem geeigneten Format zur Distribution der Anwendung (auch Rollout oder Bereitstellung genannt). Es handelt sich hierbei letztlich um zwei Binaries, einmal die CLI Executable und die AppleScript App, welche gemeinsam verteilt werden müssen.

magic-wormhole CLI Binary und wormhole.app (AppleScript-App) in einem gemeinsamen Ordner
magic-wormhole CLI Binary und wormhole.app (AppleScript-App) in einem gemeinsamen Ordner

Funktionsweise

magic-wormhole läuft plattformunabhängig und kann somit auf Linux, Windows, macOS und weiteren Betriebssystemen genutzt werden.
Um Dateien auszutauschen muss auf beiden Systemen das Tool installiert sein, der Datenaustausch erfolgt dann komfortabel über die Command Line. Die Prozedur läuft folgendermaßen ab:

  1. Anna möchte ein Datei an Bob senden, beide sind über einen bereits bestehenden Kanal miteinander verbunden, z.B. Telefon oder Instant Messaging
  2. Anna möchte nun Ihre Datei Lebenslauf_CV_en.pdf an Bob senden
  3. Anna öffnet ihr Terminal und gibt folgenden Befehl ein: wormhole send Lebenslauf_CV_en.pdf
  4. Anna erhält einen sog. "wormhole Code", dieser ist komfortabel menschenlesbar. Diesen gibt sie an Bob weiter (z.B. per Telefon, Instant Messenger, etc.)
  5. Bob öffnet sein Terminal und gibt folgenden Befehl ein: wormhole receive <wormhole-code-von-anna>
  6. Nach Bestätigung mit Y(es) wird die Datei übertragen und Bob erhält diese in seinem aktuellen Verzeichnis (pwd)

Packen und signieren der beiden Anwendungen

In diesem Abschnitt wird nun schrittweise präsentiert, wie die CLI Executable (wormhole) erstellt und signiert wird. Anschließend verfahren wir mit der wormhole.app (geschrieben in AppleScript) analog.

Das hier vorgestellte Projekt (packen und signieren der CLI Executable sowie die zugehörige AppleScript-App) auf Bitbucket: magic-wormhole-mac-binary-builder

Build Script

# --> define python main source file and application build name
app_python_main_source_filename="wormhole_main.py"
app_exec_name="wormhole"
app_icon_location="./appicon/app-icon-secure-data-transfer.icns"
bundle_identifier="<bundle-identifier-here>"
codesign_identity_hash="<codesign-identity-hash-here>"

# --> now run pyinstaller to freeze binary
pyinstaller --clean --onefile --console --osx-bundle-identifier=$bundle_identifier --codesign-identity=$codesign_identity_hash --icon=$app_icon_location --name $app_exec_name $app_python_main_source_filename

# --> copy files to release folder
mkdir $app_exec_name
cp ./dist/$app_exec_name $app_exec_name"/"$app_exec_name
cp -r ./gui/$app_exec_name".app" $app_exec_name"/"$app_exec_name".app"

# --> package into dmg
hdiutil create -volname $app_exec_name -srcfolder ./$app_exec_name -ov -format UDBZ $app_exec_name.dmg
rm -rf ./$app_exec_name

# --> sign dmg
codesign --force --deep --sign $codesign_identity_hash --timestamp $app_exec_name.dmg

Der Main Entry Point des Projektes ist die Datei build_app.sh. Das Script nutzt pyinstaller und erstellt eine CLI Executable aus wormhole_main.py und nennt diese schlicht wormhole. Das Python Skript wormhole_main.py wiederum referenziert auf die lokale Installation von magic-wormhole (danke an aquacash5), weshalb das Paket in der aktuellen Umgebung installiert sein muss (z.B. via pip install magic-wormhole, vgl. magic-wormhole auf PyPi).

wormhole CLI – Pyinstaller und Code Signing

Pyinstaller wird so gesteuert, dass es nach dem Packen die Anwendung signiert. Hierfür wird ein Code Signing Zertifikat von Apple benötigt. Damit pyinstaller die Executable signieren kann, muss folgendes mitgegeben werden: bundle_identifier und codesign-identity-hash.

Der bundle_identifier kann frei gewählt werden, wird jedoch meist in umgekehrter DNS-Notation genutzt, z.B. com.firmenname.abteilung.appname, also könnte hier z.B. lauten: com.opensourceproject.wormhole.

codesign_identity_hash gibt an, mit welcher Codesign Identität die Anwendung signiert werden soll. Voraussetzung dafür ist, dass man ein gültiges Code Signing Zertifikat von Apple besitzt und dieses in der eigenen Keychain liegt. Auf dem System verfügbare Identitäten können entweder über die Keychain.app verwaltet werden oder via Terminal.

security find-identity -p codesigning

Der Teil des codesign_identity_hash, der pyinstaller als Argument übergeben werden muss, steht am Ende des Zertifikats-Namens in Klammern.

Keychain.app zeigt (gültige) eigene Zertifikate und Code Signing Zertifikate
Keychain.app zeigt (gültige) eigene Zertifikate und Code Signing Zertifikate

Ungültige oder abgelaufene Zertifikate werden ebenfalls angezeigt, gibt man diese beim Signing an, wird der Vorgang jedoch abgebrochen.

Keychain.app zeigt gültige und ungültige, eigene Zertifikate und Code Signing Zertifikate
Keychain.app zeigt gültige und ungültige, eigene Zertifikate und Code Signing Zertifikate

Der Befehl mit dem pyinstaller nun die Executable erstellt sieht also folgendermaßen aus:

pyinstaller --clean --onefile --console --osx-bundle-identifier="com.opensourceproject.wormhole" --codesign-identity="ABCDE1F2GH" --name wormhole wormhole-main.py

pyinstaller packt nun die CLI Executable und legt diese im Unterverzeichnis ./dist mit dem Namen wormhole ab.

wormhole.app – AppleScript-App

Mit AppleScript können Aufgaben auf macOS automatisiert werden. In diesem Zusammenhang lassen sich auch einfache Dialogfelder erstellen.
Diese Funktionalität nutzen wir hier, der Benutzer wird eingangs gefragt, ob er Dateien senden oder empfangen möchte.
Fällt die Wahl auf "senden", wird ein File-Chooser-Dialog gezeigt, in dem nun die zu sendende Datei komfortable ausgewählt werden kann.
Basierend auf der Auswahl erstellt unsere AppleScript App den Befehl, mit dem wormhole dann im Terminal ausgeführt wird.

Dialoge, die unsere wormhole AppleScript App erzeugen kann
Dialoge, die unsere wormhole AppleScript App erzeugen kann

Unsere App reagiert auf die Benutzereingaben, indem es den Befehl für wormhole (send, receive, Dateiname) dynamisch erzeugt.

# set paths and locations
set binary_name to "wormhole"
set script_path to quoted form of POSIX path of (path to me)
set app_folder_path to do shell script "dirname " & script_path
set app_path to app_folder_path & "/" & binary_name

# ask user what to do
set AppInitSelector to "Welcome to " & binary_name & "! Please select SEND or RECEIVE as operation"
display dialog AppInitSelector buttons {"Cancel", "SEND", "RECEIVE"}

if button returned of result = "SEND" then
	set theFileToSend to quoted form of POSIX path of (choose file with prompt "Please select a file to send:")
	set app_parameters to "send " & theFileToSend
else if button returned of result = "RECEIVE" then
	set theReceivePhraseResponse to display dialog "Please enter receive phrase:" default answer "" buttons {"Cancel", "Continue"} default button "Continue"
	set app_parameters to "receive " & (text returned of theReceivePhraseResponse)
end if

# run magic-wormhole / SiiSecureDataTransfer in Terminal
tell application "Terminal"
	do script "cd " & app_folder_path & " && " & app_path & " " & app_parameters
	activate
end tell

Das Script kann aus dem AppleScript Editor als "Anwendung" gespeichert werden, es generiert eine App: wormhole.app. Diese kann dann – wie üblich – per Doppelklick ausgeführt werden.
Wichtig ist, dass diese App ebenfalls signiert werden muss, mit genau der selben Code Signing Identity, mit der auch die o.g. CLI Executable signiert worden ist.

Diese Anforderung ergibt sich aus der Struktur der beiden Anwendungen: wormhole.app führt wormhole im Terminal aus – daher die Notwendigkeit beide mit dem selben Key zu signieren.

Das AppleScript wormhole.applescript sowie die App wormhole.app liegen beide im Unterordner gui.

dmg erstellen

# --> copy files to release folder
mkdir wormhole
cp ./dist/wormhole ./wormhole/wormhole
cp -r ./gui/wormhole.app ./wormhole/wormhole.app

Im nächsten Schritt (nachdem pyinstaller fertig ist) erstellt unser build_app.sh einen Ordner mit dem Namen wormhole und kopiert die wormhole CLI Executable (aus dem Ordner dist) und die wormhole.app (aus dem Ordner gui) hinein.
Dieser Ordner wird nun genutzt, um daraus ein dmg zur Distribution (Verteilung, Deploy) zu erstellen.

# --> package into dmg
hdiutil create -volname wormhole -srcfolder ./wormhole -ov -format UDBZ wormhole.dmg

Das erstellte Image wormhole.dmg kann ebenfalls signiert werden. Unser build_app.sh verfährt damit wie nachfolgend gezeigt. Auch hier sollte darauf geachtet werden, dass die selbe Code Signing Identity wie bei den o.g. Anwendungen verwendet wird.

# --> sign dmg
codesign --force --deep --sign ABCDE1F2GH --timestamp wormhole.dmg

Mit folgendem Befehl kann die Signatur sowohl von CLI Executables, Apps oder Images geprüft werden:

codesign --display --verbose <path/to/file>

Notwendigkeit von Code Signing unter macOS

Code Signing wird immer dann wichtig, wenn die Anwendungen nicht nur auf dem eigenen System ausgeführt werden sollen.
Möchte man Anwendungen nur auf seinem eigenen Mac nutzen, kann man sich den Aufwand der hier gezeigten Signierung sparen.
Führt man pyinstaller ohne den Parameter --codesign-identity=XXXXXXXXXX aus, dann wird das Binary mit dem sog. adhoc Signing Mechanismus signiert und kann nur lokal ausgeführt werden (adhoc Signing ist bei Macs mit Apple M Chip zwingend notwendig).

Code Signing vs. Notarization

Ein weiterer Mechanismus, der in diesem Zusammenhang wichtig ist (auf den wir aber technisch nicht weiter eingehen), ist die Notarization.
Lädt man eine signierte, aber nicht notarisierte App aus dem Internet herunter und möchte diese starten, blockt der Gatekeeper die Ausführung.
Dies hat damit zu tun, dass Browser (wie z.B. Safari) bei geladenen Files das Extended Attribute com.apple.quarantine setzt. Ist dies bei Apps gesetzt, erschwert oder verhindert Gatekeeper die Ausführung.
Sobald das Attribut mit xattr -d com.apple.quarantine [path/to/file] von der App entfernt wurde, kann die Anwendung normal ausgeführt werden.

Zertifikate zum Code Signing unter macOS

Um ein gültiges Code Signing Zertifikat zu erhalten, muss man Mitglied beim Apple Developer Program sein und kann sich über Xcode ein gültiges Zertifikat generieren. Das geschieht in den Einstellungen von Xcode (Tastenkürzel: cmd+,) unter Accounts. Es muss für diesen Anwendungsfall – hier – ein Zertifikat vom Typ Developer ID Application erstellt werden.

Code Signing Zertifikate in Xcode erstellen: Developer ID Application
Code Signing Zertifikate in Xcode erstellen: Developer ID Application

Anschließend kann man über den Befehl alle für Code Signing verfügbare Zertifikate anzeigen:

security find-identity -p codesigning

Alternativ kann dies auch über die Keychain.app geprüft werden:

Wenn euch unsere Beiträge gefallen, dann meldet euch gerne für die Siincos News an (kein Spam, keine Werbung – versprochen!) und ihr erhaltet zu jedem neuen Beitrag direkt eine Benachrichtigung per Mail.

Mehr Content...

Habt ihr selbst Projekte oder Herausforderungen im Bereich Anwendungs- und App-Entwicklung für macOS (z.B. in Python) und benötigt eure eigenen Lösungen oder Unterstützung? Dann schreibt uns eine Mail mit eurem Anliegen und wir melden uns bei euch zurück.

Quellen

(Stand: 27.12.2023)