Sovereign-Engineering/obscuravpn-client

Obscura VPN Rust library and App (available on all major platforms)

Rust

439

1,377 commits

updated Sep 23, 2026

See the code

See what people are saying

SourceMessageScoreDate

Obscura: The first VPN that can't log your activity

(Carl from Obscura here) I love folks who are also reasoning through security models! A few things to note here: - We believe that all software running on a user's computer should be open source, so you can audit and build your own client: https://github.com/Sovereign-Engineering/obscuravpn-client…

0

Sep 22, 2026

Obscura: The first VPN that can't log your activity

We think Mullvad is a great privacy tool, which is why we partnered with them! As for what's different: We're a Multi-*Party* Relays (vs. traditional VPNs which are Single-Party Relays): https://www.privacyguides.org/articles/2024/11/17/where-are-... With Multi-Party Relays you no longer have a…

0

Sep 22, 2026

README

Obscura VPN Client

Obscura VPN library, CLI client, and App

Support

No support is provided for this code directly. However, if you are experiencing issues with your Obscura VPN service please contact support@obscura.net.

Contributions

At this time we are unable to accept external contributions. This is something that we plan to resolve soon. However until we finish the paperwork we are unable to look at any patches and will close all PRs without looking at them.

Conventions, terminology, and intended behavior are documented in the docs directory. Contributions must align with these documents or change them accordingly.

macOS App

On macOS the app installs and manages a network extension (system extension). The network extension manages the virtual device and maintains the tunnel using the Rust code as library.

Setup

  1. Setup Nix
  2. Install dependencies: nix-env -iA nixpkgs.{cmake,rustup}
  3. Open the main Xcode project
    nix develop --print-build-logs --command just xcode-open
    
  4. In Xcode, login with an account with membership in "Sovereign Engineering Inc."
    • In top status bar go to Xcode > Settings > Apple Accounts > Sign in
  5. Register development machine in Apple Developer portal (can be done in Xcode)
    1. Double click the "client" name in the folder view on the sidebar
    2. Go to Signing & Capabilities
    3. Check Automatically manage signing
    4. Make sure the team is set to Sovereign Engineering Inc.
    5. Set target to Dev Client > My Mac or any other buildable target
    6. Press "Play" aka run project
    7. Click Register Device
  6. Enable system extension developer mode
  7. Setup Developer ID provisioning profile and codesigning for Prod Client build scheme
    1. Go to https://developer.apple.com/account/resources/profiles/list
      • Download "Developer ID: System Network Extension"
      • Download "Developer ID: VPN Client App"
    2. Install both provisioning profiles by double-clicking them.
    3. Ask Carl to send the Developer ID codesigning certificate and the corresponding password
    4. Double click the certificate, enter the password, and install it to your "login" keychain which you need to select in the password input screen

Building and Running

For macOS and iOS

  1. Open the main Xcode project:

    nix develop --print-build-logs --command just xcode-open
    
  2. Pick a build scheme using Xcode's GUI, one of:

    ℹ️ INFO: Xcode differentiates between "build schemes" and "build configurations", see Apple's docs on this for more details.

    1. Dev Client: Development Client

      General purpose for development. Uses the main UI with additional developer and pre-release features exposed.

      Uses the Debug* build configurations. Codesigned with the Apple Development xcode-managed identity.

      ⚠️ WARNING: When using this build scheme, make sure you are quitting the app via the top-right status menu bar and NOT using Xcode's "Stop" as doing so does not actually stop the dev server. This is because stopping via Xcode doesn't run the build scheme's "Run → Post-actions"

    2. Prod Client: The App with a static web bundle

      Useful for reproducing what the final shippable app will look like and be built as.

      Uses the Release* build configurations. Codesigned with the Developer ID Application: Sovereign Engineering Inc. (5G943LR562) manually-managed identity.

      The static web bundle built with the build scheme's "Build → Pre-actions".

      If you encounter trouble with this build scheme, especially with codesigning or provisioning profiles:

      1. Make sure that you've completed the relevant steps in setup
      2. See additional instructions in Confirming "Developer ID" Setup
    3. Bare Client: The App with a minimal HTML UI

      Useful for fine-grain control and debugging.

      Uses the Debug* build configurations. Codesigned with the Apple Development xcode-managed identity.

  3. Build or Run the App

    • ⌘ + B (Build), or
    • ⌘ + R (Run)

    💡 TIP: It may initially seem like Xcode is doing nothing when you run or build, but it may just be running the build scheme's "Pre-actions", see the "Report navigator" in Xcode's top-left app menu: "View → Navigators → Reports" to track the actual status.

    💡 TIP: If a build fails with could not find included file 'buildversion.xcconfig' in search paths, see the relevant troubleshooting entry.


    Xcode places built products in a deeply nested directory structure that it controls, with seperate folders for each build configuration. The easiest way to locate where the app is:

    1. "Run" the app
    2. Once the app's icon appears on the macOS Dock, ⌘-Click the app icon to reveal it in the finder.

💡 TIP: It is highly recommended to read through various sections in Development Tips to better understand the various ways we've configured the Xcode build system to work with our development process.

For Android

Nix Builds

Nix builds provide an easy way to get a fully built APK. They are hermetic and reliable. However, they provide only coarse grained caching so if you are iterating during development you may prefer to use Incremental Builds.

nix build '.#apks-foss'
apksigner sign --ks your-keystore.jks --ks-pass pass:hunter2 --out=obscura-signed.apk result/app-foss-release-unsigned.apk # Sign.
adb install obscura-signed.apk # Push to your device.

Instead of app-foss-release-unsigned you can also use app-foss-debug for the debug build. Note that just the Android portion is a debug build, the Rust core and UI are still release builds.

Incremental Builds

The Android app requires a special build of the Rust library and Obscura UI. These are built using Nix, while the Android app itself can be built using Android Studio for local development, or the Gradle build system to create an official build.

  1. Build the Obscura UI
    OBS_WEB_PLATFORM="android" nix develop '.#web' --print-build-logs -c just web-bundle-build
    
  2. Build the Rust library
    nix develop '.#android' --command bash -c 'cd rustlib && cargo ndk -t arm64-v8a build --release'
    
  3. Open Android Studio and point it at the android directory, or
  4. Use Gradle to build everything
    nix develop '.#android' --command bash -c 'cd android && gradle --no-daemon $GRADLE_OPTS build'
    

In order to iterate you can just repeat the steps. 1 and 2 are only required if you changed the UI or Rust core respectively but the final APK build must always be re-run.

Gradle Dependencies

To ensure hermetic builds we pin our Gradle dependencies. If you change the dependencies you will need to regenerate the pin file.

bin/gradle-deps-update.sh

For Windows

Install Visual Studio with the following Workloads:

  • Desktop development with C++ (for Rust)
  • WinUI application development
  • Afterwards, install HeatWave for Visual Studio (for the wix-msi project)

Install Rust.

Install just (winget install Casey.Just)

Install DotNET 10.

Install nvm-windows (winget install nvm-windows) and then run nvm install lts && nvm use lts && corepack enable.

You may also need to install Windows App SDK manually to get the client app running.

Install Powershell 7

Optionally install winapp cli: winget install Microsoft.WinAppCli

On Windows, definitely ARM64 machines, you need to add C:\Program Files\Microsoft Visual Studio\18\Community\VC\Tools\Llvm\ARM64\bin to path.

Download the signed wintun 0.14.1 DLLs.

You can use Get-FileHash -Path .\wintun-0.14.1.zip -Algorithm SHA256 to verify the hash against SHA2-256: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51.

Extract to windows/wintun-0.14.1 such that windows/wintun-0.14.1/bin/arm64/wintun.dll is a file.

To test the service, if you have sudo enabled (System > Advanced settings), you can run just service. Alternatively, open an admin-enabled terminal and run cargo run --bin obscura service in the rustlib dir.

To run the debug build while the client is already installed, make sure to stop the Windows service before running the service in debug mode with the debug GUI client.

The default config directory is %APPDATA%\Obscura. When testing the service, you may find it beneficial to manually add in an account number to config.json.

To clean DNS query manually from powershell, run Remove-DnsClientNrptRule -Name "{fb157da8-6578-4f53-81ea-0a9168e96c1f}"

You might need to add a source to dotnet nuget:

  1. Navigate to windows/obscura-client.
  2. Run dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org.
  3. Download the dependencies via dotnet restore.

To run the UI, you have two options

  1. Visual Studio (open obscura-client.slnx)
  2. Run just ui [arch]

Cross-Compiling

  • If on x64, install ARM64 rustup target add aarch64-pc-windows-msvc
  • If on ARM64 install x64 rustup target add x86_64-pc-windows-msvc

The Rust service links aws-lc-sys/ring, which compiles C and assembly.

Cross-compiling x64 from a ARM64 host:

  1. NASM + Ninja installed,

    winget install NASM.NASM Ninja-build.Ninja
    

    Add NASM installation (%LOCALAPPDATA%\bin\NASM) to PATH environment variable.

  2. When building obscura-client.csproj for x64 on ARM64, AWS_LC_SYS_CMAKE_BUILDER=1;CMAKE_GENERATOR=Ninja is automatically set.

Cross-compiling ARM64 on a x64 host has the following requirements:

  1. Have NASM installed
  2. When building obscura-client.csproj on x64, AWS_LC_SYS_CMAKE_BUILDER=0 is automatically set, forcing aws-lc-sys's CMake-free cc builder (the CMake builder nests object paths past Windows' MAX_PATH).

Tips

To override the version (or any other properties) of a build, you can set the OBSCURA_VERSION environment variable in obscura-client/local.props and wix-msi/local.props.

<!-- windows/wix-msi/local.props -->
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OBSCURA_VERSION>v0.165.2</OBSCURA_VERSION>
  </PropertyGroup>
</Project>

GUI logs are written to %LOCALAPPDATA%\Obscura\logs

When service is running as as system service, the config file is written to %SystemRoot%\System32\config\systemprofile\AppData\Local\Obscura

The WinUI 3 Gallery app is very useful at showcasing features currently available with code snippets.

Segoe Fluent Icons

When updating the .NET version, ensure windows-build.yml is updated as well.

Packaging

Inside Visual Studio,

  1. obscura-client unpackaged should be selected at the top.
  2. Select Release, x64 (or ARM64).
  3. Build the wix-msi project.

Note this requires a self signed certificates to test.

Swift unit tests

"Swift Testing" tests are placed in *Test.swift files, which need to be a member of the Tests target. Testing (not running) with the Tests scheme builds and executes all tests.

Debugging

Logs

Both app and network extension logs are available via Apple's unified logging system.

Analyzing Logs

There are tools for analyzing logs available as bin/log-*. They accept log files in JSON lines format. This can be found in the app's Debug Bundle or from the Apple log command by specifying --style=ndjson.

The main tool is bin/log-text.py which just turns the logs into a readable text format as well as applying some basic filtering with a few CLI options to apply more filters. Other tools are available, run with --help to get information about what they do.

For more in-depth analysis you are likely best using the tools as a starting point and modifying them as needed or using other tools like jq, sqlite or duckdb. If your analysis is generally useful consider committing it.

Stream Logs

This will output logs starting at the point in time when you run this command:

log stream --info --debug --predicate 'process CONTAINS[c] "obscura" || subsystem CONTAINS[c] "obscura"'

View Past Logs

[!WARNING] Since Apple may or may not persist logs at the INFO or DEBUG level, logs at these level might be lost. See Apple's developer docs on this for more information.

You may be able to set a log configuration to ensure that these logs are persisted, though this has not been tested, please update this README with instructions if you successfully test this. See Apple's docs on "Customizing Logging Behavior While Debugging" for more information.

log show --last 200 --info --debug --color always --predicate 'process CONTAINS[c] "obscura" || subsystem CONTAINS[c] "obscura"' | less +G -R

UserDefaults

defaults read "net.obscura.vpn-client-app"
# delete all defaults including Sparkle related keys (SU*)
defaults delete-all "net.obscura.vpn-client-app"
# delete keys individually
defaults delete "net.obscura.vpn-client-app" <key>

Running Checks

Linting

nix develop --print-build-logs --command just lint

Formatting

Checking

nix flake check

Auto-fixing

nix develop --print-build-logs --command just format-fix

Building a Notarized Disk Image

  1. Save authentication credentials for the Apple notary service (only need to do once)

    xcrun notarytool store-credentials "notarytool-password" --team-id 5G943LR562
    

    Use appleid.apple.com --> App-Specific Passwords

  2. (OPTIONAL) If we're doing a release, tag the version git tag -s v/1.23 -m v/1.23 && git push --tags.

  3. Unlock the "Login" keychain: security unlock-keychain

  4. Build the signed and notarized disk image: just build-dmg

    💡 TIP: This command uses AppleScript automation of Finder to change the background of Disk Images, so Finder windows may open.

    The built disk image will appear in the current working directory as "Obscura VPN.dmg"

Troubleshooting

cargo not rebuilding when it should

A lot of Xcode-set properties don't properly trigger a rebuild from cargo even though they're supposed to. The most prominent of which is MACOSX_DEPLOYMENT_TARGET.

This is easily worked-around by "Product → Clean Build Folder..." in Xcode then rerunning the build.

Upstream status on this:

Development Tips

Enabling system extension developer mode

This is necessary for:

  • The systemextensionsctl commands to work, and
  • To allow installing and running system extensions from places other than /Applications

According to Apple's docs for system extensions, as of 2024-07-04:

You must place all system extensions in the Contents/Library/SystemExtensions directory of your app bundle, and the app itself must be installed in one of the system’s Applications directories. To allow development of your app outside of these directories, use the systemextensionsctl command-line tool to enable developer mode. When in developer mode, the system doesn't check the location of your system extension prior to loading it, so you can load it from anywhere in the file system.

To accomplish this:

  1. Disable system integrity protection
  2. Then, run
    systemextensionsctl developer on
    

Removing network extension (system extension)

  1. Ensure that system extension developer mode is enabled
  2. Then, run
    systemextensionsctl uninstall 5G943LR562 net.obscura.vpn-client-app.system-network-extension
    

Nix Setup

  • Install nix (only the package manager is needed)

  • Enable flakes

    Add the following to ~/.config/nix/nix.conf or /etc/nix/nix.conf:

    experimental-features = nix-command flakes
    
  • Optional, but strongly recommended: Set up nix-direnv and integrate it with your preferred shell

    If you do this, you can omit the nix develop ... --command parts, as cd-ing into the repository directory will set up your environment variables with the correct tools as long as you've direnv allow-ed the directory.

Confirming "Developer ID" Setup

To confirm that the Developer ID provisioning profile and codesigning are set up correctly (required for the Prod Client build scheme):

  1. Pick the Prod Client build scheme in Xcode
  2. Create an Archive Choose from Xcode's top-left app menu: "Product → Archive"
  3. Ensure that the "Archive" action succeeds in the "Report navigator" Choose from Xcode's top-left app menu: "View → Navigators → Reports"

Linux

For local development, build and run any of the binaries (each builds in the same Debian container as the release):

  • contrib/bin/linux_run_gui.sh: builds and runs the GUI.
  • contrib/bin/linux_run_cli.sh: builds and runs the obscura CLI, passing its arguments through.
  • contrib/bin/linux_run_service.sh: builds and runs the obscura system service the GUI and CLI talk to.

Supported distributions

The released packages support:

  • Debian 13
  • Ubuntu 26.04
  • Fedora 44
  • RHEL 10 (requires EPEL)
  • Arch Linux

Building and signing packages

Build all the packages (obscura-cli, obscura-gui, obscura, plus obscura-repository for deb and rpm, obscura-keyring for arch) and the signed apt/dnf/pacman repositories:

./contrib/bin/linux-build-packages.bash

It derives the signing key from linux/signing_keys/current.public.asc (exporting its secret from your gpg keyring) and prompts for its passphrase. Publish the three repository trees it produces, result-linux/dist-prod/{deb,rpm,arch}, at https://linux-pkgs.obscura.com/{deb,rpm,arch}. Pass --test to build instead with the committed keys in linux/signing_keys_test/. The resulting result-linux/dist-test repository packages assume the directory is served at http://10.0.2.2:54321, which is the host from a QEMU guest with user-mode networking. Pass --dirty to build production packages from an untagged or modified tree.

Signing key rotation

linux/signing_keys/ holds current.public.asc (the public key of the keypair whose private key signs releases), next.public.asc (the public key of the next keypair, shipped ahead), and revocation.asc (the public key of every rotated-out keypair, with its revocation certificate). It also holds rotate_signing_key.bash. The directory is self-contained: copy it to an ephemeral machine along with the directory of encrypted private keys, run the script there, and copy current.public.asc, next.public.asc, and revocation.asc back.

User machines that already trust a rotated-out public key must stop trusting it. The packaging scripts and the packages they ship handle this automatically; each format uses a different mechanism:

  • deb: the keyring file shipped by the obscura-repository package is built from just current.public.asc and next.public.asc, and upgrades replace it wholesale, so a rotated-out public key disappears from every user machine on its own.
  • rpm: the obscura-repository package ships RPM-GPG-KEY-obscura, built from just current.public.asc and next.public.asc (so new installs never trust a rotated-out public key), and RPM-GPG-KEY-obscura-revoked, listing the fingerprints from revocation.asc. Its obscura-package-signing-key-refresh.timer runs daily, importing RPM-GPG-KEY-obscura and removing the public keys listed in RPM-GPG-KEY-obscura-revoked from the rpm database. The timer is needed because upgrading the package only replaces the public key files: rpm never re-reads them on its own, and scriptlets cannot import public keys while the transaction lock is held.
  • arch: the obscura-keyring package ships obscura.gpg, built from current.public.asc, next.public.asc, and revocation.asc, plus obscura-trusted and obscura-revoked, listing the fingerprints of the trusted and revoked public keys respectively. Its install hook runs pacman-key --populate obscura on every install and upgrade, which imports the new public keys and disables the revoked ones.
android
ios
linux
macos
quic
rust
windows
wireguard

Contributors

elibroftw

536 commits

FlorianUekermann

316 commits

kevincox

233 commits

francesca64

147 commits

Sovereign-Engineering/obscuravpn-client

Obscura VPN Rust library and App (available on all major platforms)

Rust

439

1,377 commits

updated Sep 23, 2026

See the code

See what people are saying

SourceMessageScoreDate

Obscura: The first VPN that can't log your activity

(Carl from Obscura here) I love folks who are also reasoning through security models! A few things to note here: - We believe that all software running on a user's computer should be open source, so you can audit and build your own client: https://github.com/Sovereign-Engineering/obscuravpn-client…

0

Sep 22, 2026

Obscura: The first VPN that can't log your activity

We think Mullvad is a great privacy tool, which is why we partnered with them! As for what's different: We're a Multi-*Party* Relays (vs. traditional VPNs which are Single-Party Relays): https://www.privacyguides.org/articles/2024/11/17/where-are-... With Multi-Party Relays you no longer have a…

0

Sep 22, 2026

README

Obscura VPN Client

Obscura VPN library, CLI client, and App

Support

No support is provided for this code directly. However, if you are experiencing issues with your Obscura VPN service please contact support@obscura.net.

Contributions

At this time we are unable to accept external contributions. This is something that we plan to resolve soon. However until we finish the paperwork we are unable to look at any patches and will close all PRs without looking at them.

Conventions, terminology, and intended behavior are documented in the docs directory. Contributions must align with these documents or change them accordingly.

macOS App

On macOS the app installs and manages a network extension (system extension). The network extension manages the virtual device and maintains the tunnel using the Rust code as library.

Setup

  1. Setup Nix
  2. Install dependencies: nix-env -iA nixpkgs.{cmake,rustup}
  3. Open the main Xcode project
    nix develop --print-build-logs --command just xcode-open
    
  4. In Xcode, login with an account with membership in "Sovereign Engineering Inc."
    • In top status bar go to Xcode > Settings > Apple Accounts > Sign in
  5. Register development machine in Apple Developer portal (can be done in Xcode)
    1. Double click the "client" name in the folder view on the sidebar
    2. Go to Signing & Capabilities
    3. Check Automatically manage signing
    4. Make sure the team is set to Sovereign Engineering Inc.
    5. Set target to Dev Client > My Mac or any other buildable target
    6. Press "Play" aka run project
    7. Click Register Device
  6. Enable system extension developer mode
  7. Setup Developer ID provisioning profile and codesigning for Prod Client build scheme
    1. Go to https://developer.apple.com/account/resources/profiles/list
      • Download "Developer ID: System Network Extension"
      • Download "Developer ID: VPN Client App"
    2. Install both provisioning profiles by double-clicking them.
    3. Ask Carl to send the Developer ID codesigning certificate and the corresponding password
    4. Double click the certificate, enter the password, and install it to your "login" keychain which you need to select in the password input screen

Building and Running

For macOS and iOS

  1. Open the main Xcode project:

    nix develop --print-build-logs --command just xcode-open
    
  2. Pick a build scheme using Xcode's GUI, one of:

    ℹ️ INFO: Xcode differentiates between "build schemes" and "build configurations", see Apple's docs on this for more details.

    1. Dev Client: Development Client

      General purpose for development. Uses the main UI with additional developer and pre-release features exposed.

      Uses the Debug* build configurations. Codesigned with the Apple Development xcode-managed identity.

      ⚠️ WARNING: When using this build scheme, make sure you are quitting the app via the top-right status menu bar and NOT using Xcode's "Stop" as doing so does not actually stop the dev server. This is because stopping via Xcode doesn't run the build scheme's "Run → Post-actions"

    2. Prod Client: The App with a static web bundle

      Useful for reproducing what the final shippable app will look like and be built as.

      Uses the Release* build configurations. Codesigned with the Developer ID Application: Sovereign Engineering Inc. (5G943LR562) manually-managed identity.

      The static web bundle built with the build scheme's "Build → Pre-actions".

      If you encounter trouble with this build scheme, especially with codesigning or provisioning profiles:

      1. Make sure that you've completed the relevant steps in setup
      2. See additional instructions in Confirming "Developer ID" Setup
    3. Bare Client: The App with a minimal HTML UI

      Useful for fine-grain control and debugging.

      Uses the Debug* build configurations. Codesigned with the Apple Development xcode-managed identity.

  3. Build or Run the App

    • ⌘ + B (Build), or
    • ⌘ + R (Run)

    💡 TIP: It may initially seem like Xcode is doing nothing when you run or build, but it may just be running the build scheme's "Pre-actions", see the "Report navigator" in Xcode's top-left app menu: "View → Navigators → Reports" to track the actual status.

    💡 TIP: If a build fails with could not find included file 'buildversion.xcconfig' in search paths, see the relevant troubleshooting entry.


    Xcode places built products in a deeply nested directory structure that it controls, with seperate folders for each build configuration. The easiest way to locate where the app is:

    1. "Run" the app
    2. Once the app's icon appears on the macOS Dock, ⌘-Click the app icon to reveal it in the finder.

💡 TIP: It is highly recommended to read through various sections in Development Tips to better understand the various ways we've configured the Xcode build system to work with our development process.

For Android

Nix Builds

Nix builds provide an easy way to get a fully built APK. They are hermetic and reliable. However, they provide only coarse grained caching so if you are iterating during development you may prefer to use Incremental Builds.

nix build '.#apks-foss'
apksigner sign --ks your-keystore.jks --ks-pass pass:hunter2 --out=obscura-signed.apk result/app-foss-release-unsigned.apk # Sign.
adb install obscura-signed.apk # Push to your device.

Instead of app-foss-release-unsigned you can also use app-foss-debug for the debug build. Note that just the Android portion is a debug build, the Rust core and UI are still release builds.

Incremental Builds

The Android app requires a special build of the Rust library and Obscura UI. These are built using Nix, while the Android app itself can be built using Android Studio for local development, or the Gradle build system to create an official build.

  1. Build the Obscura UI
    OBS_WEB_PLATFORM="android" nix develop '.#web' --print-build-logs -c just web-bundle-build
    
  2. Build the Rust library
    nix develop '.#android' --command bash -c 'cd rustlib && cargo ndk -t arm64-v8a build --release'
    
  3. Open Android Studio and point it at the android directory, or
  4. Use Gradle to build everything
    nix develop '.#android' --command bash -c 'cd android && gradle --no-daemon $GRADLE_OPTS build'
    

In order to iterate you can just repeat the steps. 1 and 2 are only required if you changed the UI or Rust core respectively but the final APK build must always be re-run.

Gradle Dependencies

To ensure hermetic builds we pin our Gradle dependencies. If you change the dependencies you will need to regenerate the pin file.

bin/gradle-deps-update.sh

For Windows

Install Visual Studio with the following Workloads:

  • Desktop development with C++ (for Rust)
  • WinUI application development
  • Afterwards, install HeatWave for Visual Studio (for the wix-msi project)

Install Rust.

Install just (winget install Casey.Just)

Install DotNET 10.

Install nvm-windows (winget install nvm-windows) and then run nvm install lts && nvm use lts && corepack enable.

You may also need to install Windows App SDK manually to get the client app running.

Install Powershell 7

Optionally install winapp cli: winget install Microsoft.WinAppCli

On Windows, definitely ARM64 machines, you need to add C:\Program Files\Microsoft Visual Studio\18\Community\VC\Tools\Llvm\ARM64\bin to path.

Download the signed wintun 0.14.1 DLLs.

You can use Get-FileHash -Path .\wintun-0.14.1.zip -Algorithm SHA256 to verify the hash against SHA2-256: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51.

Extract to windows/wintun-0.14.1 such that windows/wintun-0.14.1/bin/arm64/wintun.dll is a file.

To test the service, if you have sudo enabled (System > Advanced settings), you can run just service. Alternatively, open an admin-enabled terminal and run cargo run --bin obscura service in the rustlib dir.

To run the debug build while the client is already installed, make sure to stop the Windows service before running the service in debug mode with the debug GUI client.

The default config directory is %APPDATA%\Obscura. When testing the service, you may find it beneficial to manually add in an account number to config.json.

To clean DNS query manually from powershell, run Remove-DnsClientNrptRule -Name "{fb157da8-6578-4f53-81ea-0a9168e96c1f}"

You might need to add a source to dotnet nuget:

  1. Navigate to windows/obscura-client.
  2. Run dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org.
  3. Download the dependencies via dotnet restore.

To run the UI, you have two options

  1. Visual Studio (open obscura-client.slnx)
  2. Run just ui [arch]

Cross-Compiling

  • If on x64, install ARM64 rustup target add aarch64-pc-windows-msvc
  • If on ARM64 install x64 rustup target add x86_64-pc-windows-msvc

The Rust service links aws-lc-sys/ring, which compiles C and assembly.

Cross-compiling x64 from a ARM64 host:

  1. NASM + Ninja installed,

    winget install NASM.NASM Ninja-build.Ninja
    

    Add NASM installation (%LOCALAPPDATA%\bin\NASM) to PATH environment variable.

  2. When building obscura-client.csproj for x64 on ARM64, AWS_LC_SYS_CMAKE_BUILDER=1;CMAKE_GENERATOR=Ninja is automatically set.

Cross-compiling ARM64 on a x64 host has the following requirements:

  1. Have NASM installed
  2. When building obscura-client.csproj on x64, AWS_LC_SYS_CMAKE_BUILDER=0 is automatically set, forcing aws-lc-sys's CMake-free cc builder (the CMake builder nests object paths past Windows' MAX_PATH).

Tips

To override the version (or any other properties) of a build, you can set the OBSCURA_VERSION environment variable in obscura-client/local.props and wix-msi/local.props.

<!-- windows/wix-msi/local.props -->
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OBSCURA_VERSION>v0.165.2</OBSCURA_VERSION>
  </PropertyGroup>
</Project>

GUI logs are written to %LOCALAPPDATA%\Obscura\logs

When service is running as as system service, the config file is written to %SystemRoot%\System32\config\systemprofile\AppData\Local\Obscura

The WinUI 3 Gallery app is very useful at showcasing features currently available with code snippets.

Segoe Fluent Icons

When updating the .NET version, ensure windows-build.yml is updated as well.

Packaging

Inside Visual Studio,

  1. obscura-client unpackaged should be selected at the top.
  2. Select Release, x64 (or ARM64).
  3. Build the wix-msi project.

Note this requires a self signed certificates to test.

Swift unit tests

"Swift Testing" tests are placed in *Test.swift files, which need to be a member of the Tests target. Testing (not running) with the Tests scheme builds and executes all tests.

Debugging

Logs

Both app and network extension logs are available via Apple's unified logging system.

Analyzing Logs

There are tools for analyzing logs available as bin/log-*. They accept log files in JSON lines format. This can be found in the app's Debug Bundle or from the Apple log command by specifying --style=ndjson.

The main tool is bin/log-text.py which just turns the logs into a readable text format as well as applying some basic filtering with a few CLI options to apply more filters. Other tools are available, run with --help to get information about what they do.

For more in-depth analysis you are likely best using the tools as a starting point and modifying them as needed or using other tools like jq, sqlite or duckdb. If your analysis is generally useful consider committing it.

Stream Logs

This will output logs starting at the point in time when you run this command:

log stream --info --debug --predicate 'process CONTAINS[c] "obscura" || subsystem CONTAINS[c] "obscura"'

View Past Logs

[!WARNING] Since Apple may or may not persist logs at the INFO or DEBUG level, logs at these level might be lost. See Apple's developer docs on this for more information.

You may be able to set a log configuration to ensure that these logs are persisted, though this has not been tested, please update this README with instructions if you successfully test this. See Apple's docs on "Customizing Logging Behavior While Debugging" for more information.

log show --last 200 --info --debug --color always --predicate 'process CONTAINS[c] "obscura" || subsystem CONTAINS[c] "obscura"' | less +G -R

UserDefaults

defaults read "net.obscura.vpn-client-app"
# delete all defaults including Sparkle related keys (SU*)
defaults delete-all "net.obscura.vpn-client-app"
# delete keys individually
defaults delete "net.obscura.vpn-client-app" <key>

Running Checks

Linting

nix develop --print-build-logs --command just lint

Formatting

Checking

nix flake check

Auto-fixing

nix develop --print-build-logs --command just format-fix

Building a Notarized Disk Image

  1. Save authentication credentials for the Apple notary service (only need to do once)

    xcrun notarytool store-credentials "notarytool-password" --team-id 5G943LR562
    

    Use appleid.apple.com --> App-Specific Passwords

  2. (OPTIONAL) If we're doing a release, tag the version git tag -s v/1.23 -m v/1.23 && git push --tags.

  3. Unlock the "Login" keychain: security unlock-keychain

  4. Build the signed and notarized disk image: just build-dmg

    💡 TIP: This command uses AppleScript automation of Finder to change the background of Disk Images, so Finder windows may open.

    The built disk image will appear in the current working directory as "Obscura VPN.dmg"

Troubleshooting

cargo not rebuilding when it should

A lot of Xcode-set properties don't properly trigger a rebuild from cargo even though they're supposed to. The most prominent of which is MACOSX_DEPLOYMENT_TARGET.

This is easily worked-around by "Product → Clean Build Folder..." in Xcode then rerunning the build.

Upstream status on this:

Development Tips

Enabling system extension developer mode

This is necessary for:

  • The systemextensionsctl commands to work, and
  • To allow installing and running system extensions from places other than /Applications

According to Apple's docs for system extensions, as of 2024-07-04:

You must place all system extensions in the Contents/Library/SystemExtensions directory of your app bundle, and the app itself must be installed in one of the system’s Applications directories. To allow development of your app outside of these directories, use the systemextensionsctl command-line tool to enable developer mode. When in developer mode, the system doesn't check the location of your system extension prior to loading it, so you can load it from anywhere in the file system.

To accomplish this:

  1. Disable system integrity protection
  2. Then, run
    systemextensionsctl developer on
    

Removing network extension (system extension)

  1. Ensure that system extension developer mode is enabled
  2. Then, run
    systemextensionsctl uninstall 5G943LR562 net.obscura.vpn-client-app.system-network-extension
    

Nix Setup

  • Install nix (only the package manager is needed)

  • Enable flakes

    Add the following to ~/.config/nix/nix.conf or /etc/nix/nix.conf:

    experimental-features = nix-command flakes
    
  • Optional, but strongly recommended: Set up nix-direnv and integrate it with your preferred shell

    If you do this, you can omit the nix develop ... --command parts, as cd-ing into the repository directory will set up your environment variables with the correct tools as long as you've direnv allow-ed the directory.

Confirming "Developer ID" Setup

To confirm that the Developer ID provisioning profile and codesigning are set up correctly (required for the Prod Client build scheme):

  1. Pick the Prod Client build scheme in Xcode
  2. Create an Archive Choose from Xcode's top-left app menu: "Product → Archive"
  3. Ensure that the "Archive" action succeeds in the "Report navigator" Choose from Xcode's top-left app menu: "View → Navigators → Reports"

Linux

For local development, build and run any of the binaries (each builds in the same Debian container as the release):

  • contrib/bin/linux_run_gui.sh: builds and runs the GUI.
  • contrib/bin/linux_run_cli.sh: builds and runs the obscura CLI, passing its arguments through.
  • contrib/bin/linux_run_service.sh: builds and runs the obscura system service the GUI and CLI talk to.

Supported distributions

The released packages support:

  • Debian 13
  • Ubuntu 26.04
  • Fedora 44
  • RHEL 10 (requires EPEL)
  • Arch Linux

Building and signing packages

Build all the packages (obscura-cli, obscura-gui, obscura, plus obscura-repository for deb and rpm, obscura-keyring for arch) and the signed apt/dnf/pacman repositories:

./contrib/bin/linux-build-packages.bash

It derives the signing key from linux/signing_keys/current.public.asc (exporting its secret from your gpg keyring) and prompts for its passphrase. Publish the three repository trees it produces, result-linux/dist-prod/{deb,rpm,arch}, at https://linux-pkgs.obscura.com/{deb,rpm,arch}. Pass --test to build instead with the committed keys in linux/signing_keys_test/. The resulting result-linux/dist-test repository packages assume the directory is served at http://10.0.2.2:54321, which is the host from a QEMU guest with user-mode networking. Pass --dirty to build production packages from an untagged or modified tree.

Signing key rotation

linux/signing_keys/ holds current.public.asc (the public key of the keypair whose private key signs releases), next.public.asc (the public key of the next keypair, shipped ahead), and revocation.asc (the public key of every rotated-out keypair, with its revocation certificate). It also holds rotate_signing_key.bash. The directory is self-contained: copy it to an ephemeral machine along with the directory of encrypted private keys, run the script there, and copy current.public.asc, next.public.asc, and revocation.asc back.

User machines that already trust a rotated-out public key must stop trusting it. The packaging scripts and the packages they ship handle this automatically; each format uses a different mechanism:

  • deb: the keyring file shipped by the obscura-repository package is built from just current.public.asc and next.public.asc, and upgrades replace it wholesale, so a rotated-out public key disappears from every user machine on its own.
  • rpm: the obscura-repository package ships RPM-GPG-KEY-obscura, built from just current.public.asc and next.public.asc (so new installs never trust a rotated-out public key), and RPM-GPG-KEY-obscura-revoked, listing the fingerprints from revocation.asc. Its obscura-package-signing-key-refresh.timer runs daily, importing RPM-GPG-KEY-obscura and removing the public keys listed in RPM-GPG-KEY-obscura-revoked from the rpm database. The timer is needed because upgrading the package only replaces the public key files: rpm never re-reads them on its own, and scriptlets cannot import public keys while the transaction lock is held.
  • arch: the obscura-keyring package ships obscura.gpg, built from current.public.asc, next.public.asc, and revocation.asc, plus obscura-trusted and obscura-revoked, listing the fingerprints of the trusted and revoked public keys respectively. Its install hook runs pacman-key --populate obscura on every install and upgrade, which imports the new public keys and disables the revoked ones.
android
ios
linux
macos
quic
rust
windows
wireguard

Contributors

elibroftw

536 commits

FlorianUekermann

316 commits

kevincox

233 commits

francesca64

147 commits

Languages

Rust

40.5%

Swift

18.4%

TypeScript

16.2%

C#

10.1%

Kotlin

7.8%

Shell

2.4%

Nix

1.1%