{ lib, stdenv, fetchurl, makeDesktopItem, copyDesktopItems, makeWrapper, writeText, autoPatchelfHook, patchelfUnstable, # have to use patchelfUnstable to support --no-clobber-old-sections wrapGAppsHook3, callPackage, atk, cairo, dbus, dbus-glib, fontconfig, freetype, gdk-pixbuf, glib, gtk3, libxcb, libX11, libXext, libXrender, libXt, libXtst, mesa, pango, pciutils, zlib, libnotifySupport ? stdenv.hostPlatform.isLinux, libnotify, waylandSupport ? stdenv.hostPlatform.isLinux, libxkbcommon, libdrm, libGL, mediaSupport ? true, ffmpeg, audioSupport ? mediaSupport, pipewireSupport ? audioSupport, pipewire, pulseaudioSupport ? audioSupport, libpulseaudio, apulse, alsa-lib, libvaSupport ? mediaSupport, libva, # Hardening graphene-hardened-malloc, # Whether to use graphene-hardened-malloc useHardenedMalloc ? null, # Whether to disable multiprocess support disableContentSandbox ? false, # Extra preferences extraPrefs ? "", }: lib.warnIf (useHardenedMalloc != null) "tor-browser: useHardenedMalloc is deprecated and enabling it can cause issues" ( let libPath = lib.makeLibraryPath ( [ alsa-lib atk cairo dbus dbus-glib fontconfig freetype gdk-pixbuf glib gtk3 libxcb libX11 libXext libXrender libXt libXtst mesa # for libgbm pango pciutils stdenv.cc.cc stdenv.cc.libc zlib ] ++ lib.optionals libnotifySupport [ libnotify ] ++ lib.optionals waylandSupport [ libxkbcommon libdrm libGL ] ++ lib.optionals pipewireSupport [ pipewire ] ++ lib.optionals pulseaudioSupport [ libpulseaudio ] ++ lib.optionals libvaSupport [ libva ] ++ lib.optionals mediaSupport [ ffmpeg ] ); version = "14.0.3"; sources = { x86_64-linux = fetchurl { urls = [ "https://archive.torproject.org/tor-package-archive/torbrowser/${version}/tor-browser-linux-x86_64-${version}.tar.xz" "https://dist.torproject.org/torbrowser/${version}/tor-browser-linux-x86_64-${version}.tar.xz" "https://tor.eff.org/dist/torbrowser/${version}/tor-browser-linux-x86_64-${version}.tar.xz" "https://tor.calyxinstitute.org/dist/torbrowser/${version}/tor-browser-linux-x86_64-${version}.tar.xz" ]; hash = "sha256-WddDs5lQFZde8Qy/7nQhGTrrT9BiVswriqOpPVpgvwY="; }; i686-linux = fetchurl { urls = [ "https://archive.torproject.org/tor-package-archive/torbrowser/${version}/tor-browser-linux-i686-${version}.tar.xz" "https://dist.torproject.org/torbrowser/${version}/tor-browser-linux-i686-${version}.tar.xz" "https://tor.eff.org/dist/torbrowser/${version}/tor-browser-linux-i686-${version}.tar.xz" "https://tor.calyxinstitute.org/dist/torbrowser/${version}/tor-browser-linux-i686-${version}.tar.xz" ]; hash = "sha256-DsTJiZkw0g4ip/yAwQ9IomZHQ6RP0hFNEzVJ8/fEbyQ="; }; }; distributionIni = writeText "distribution.ini" ( lib.generators.toINI { } { # Some light branding indicating this build uses our distro preferences Global = { id = "nixos"; version = "1.0"; about = "Tor Browser for NixOS"; }; } ); policiesJson = writeText "policies.json" ( builtins.toJSON { policies.DisableAppUpdate = true; } ); in stdenv.mkDerivation rec { pname = "tor-browser"; inherit version; src = sources.${stdenv.hostPlatform.system} or (throw "unsupported system: ${stdenv.hostPlatform.system}"); nativeBuildInputs = [ autoPatchelfHook patchelfUnstable copyDesktopItems makeWrapper wrapGAppsHook3 ]; buildInputs = [ gtk3 alsa-lib dbus-glib libXtst ]; # Firefox uses "relrhack" to manually process relocations from a fixed offset patchelfFlags = [ "--no-clobber-old-sections" ]; preferLocalBuild = true; allowSubstitutes = false; desktopItems = [ (makeDesktopItem { name = "torbrowser"; exec = "tor-browser %U"; icon = "tor-browser"; desktopName = "Tor Browser"; genericName = "Web Browser"; comment = meta.description; categories = [ "Network" "WebBrowser" "Security" ]; mimeTypes = [ "text/html" "text/xml" "application/xhtml+xml" "application/vnd.mozilla.xul+xml" "x-scheme-handler/http" "x-scheme-handler/https" ]; }) ]; buildPhase = '' runHook preBuild # For convenience ... TBB_IN_STORE=$out/share/tor-browser # Unpack & enter mkdir -p "$TBB_IN_STORE" tar xf "$src" -C "$TBB_IN_STORE" --strip-components=2 pushd "$TBB_IN_STORE" # Set ELF interpreter autoPatchelf firefox.real TorBrowser/Tor # firefox is a wrapper that checks for a more recent libstdc++ & appends it to the ld path mv firefox.real firefox # store state at `~/.tor browser` instead of relative to executable touch "$TBB_IN_STORE/system-install" # The final libPath. Note, we could split this into firefoxLibPath # and torLibPath for accuracy, but this is more convenient ... libPath=${libPath}:$TBB_IN_STORE:$TBB_IN_STORE/TorBrowser/Tor # apulse uses a non-standard library path. For now special-case it. ${lib.optionalString (audioSupport && !pulseaudioSupport) '' libPath=${apulse}/lib/apulse:$libPath ''} # Fixup paths to pluggable transports. substituteInPlace TorBrowser/Data/Tor/torrc-defaults \ --replace-fail './TorBrowser' "$TBB_IN_STORE/TorBrowser" # Prepare for autoconfig. # # See https://developer.mozilla.org/en-US/Firefox/Enterprise_deployment cat >defaults/pref/autoconfig.js <<EOF // pref("general.config.filename", "mozilla.cfg"); pref("general.config.obscure_value", 0); EOF # Hard-coded Firefox preferences. cat >mozilla.cfg <<EOF // First line must be a comment // Reset pref that captures store paths. clearPref("extensions.xpiState"); // Stop obnoxious first-run redirection. lockPref("noscript.firstRunRedirection", false); // User should never change these. Locking prevents these // values from being written to prefs.js, avoiding Store // path capture. lockPref("extensions.torlauncher.torrc-defaults_path", "$TBB_IN_STORE/TorBrowser/Data/Tor/torrc-defaults"); lockPref("extensions.torlauncher.tor_path", "$TBB_IN_STORE/TorBrowser/Tor/tor"); // Insist on using IPC for communicating with Tor // // Defaults to creating \$XDG_RUNTIME_DIR/Tor/{socks,control}.socket lockPref("extensions.torlauncher.control_port_use_ipc", true); lockPref("extensions.torlauncher.socks_port_use_ipc", true); // Optionally disable multiprocess support. We always set this to ensure that // toggling the pref takes effect. lockPref("browser.tabs.remote.autostart.2", ${if disableContentSandbox then "false" else "true"}); // Allow sandbox access to sound devices if using ALSA directly ${ if (audioSupport && !pulseaudioSupport) then '' pref("security.sandbox.content.write_path_whitelist", "/dev/snd/"); '' else '' clearPref("security.sandbox.content.write_path_whitelist"); '' } ${lib.optionalString (extraPrefs != "") '' ${extraPrefs} ''} EOF # FONTCONFIG_FILE is required to make fontconfig read the TBB # fonts.conf; upstream uses FONTCONFIG_PATH, but FC_DEBUG=1024 # indicates the system fonts.conf being used instead. FONTCONFIG_FILE=$TBB_IN_STORE/fontconfig/fonts.conf substituteInPlace "$FONTCONFIG_FILE" \ --replace-fail '<dir prefix="cwd">fonts</dir>' "<dir>$TBB_IN_STORE/fonts</dir>" # Hard-code paths to geoip data files. TBB resolves the geoip files # relative to torrc-defaults_path but if we do not hard-code them # here, these paths end up being written to the torrc in the user's # state dir. cat >>TorBrowser/Data/Tor/torrc-defaults <<EOF GeoIPFile $TBB_IN_STORE/TorBrowser/Data/Tor/geoip GeoIPv6File $TBB_IN_STORE/TorBrowser/Data/Tor/geoip6 EOF mkdir -p $out/bin makeWrapper "$TBB_IN_STORE/firefox" "$out/bin/tor-browser" \ --prefix LD_PRELOAD : "${ lib.optionalString ( useHardenedMalloc == true ) "${graphene-hardened-malloc}/lib/libhardened_malloc.so" }" \ --prefix LD_LIBRARY_PATH : "$libPath" \ --set FONTCONFIG_FILE "$FONTCONFIG_FILE" \ --set-default MOZ_ENABLE_WAYLAND 1 # Easier access to docs mkdir -p $out/share/doc ln -s $TBB_IN_STORE/TorBrowser/Docs $out/share/doc/tor-browser # Install icons for i in 16 32 48 64 128; do mkdir -p $out/share/icons/hicolor/''${i}x''${i}/apps/ ln -s $out/share/tor-browser/browser/chrome/icons/default/default$i.png $out/share/icons/hicolor/''${i}x''${i}/apps/tor-browser.png done # Check installed apps echo "Checking bundled Tor ..." LD_LIBRARY_PATH=$libPath $TBB_IN_STORE/TorBrowser/Tor/tor --version >/dev/null echo "Checking tor-browser wrapper ..." $out/bin/tor-browser --version >/dev/null runHook postBuild ''; installPhase = '' runHook preInstall # Install distribution customizations install -Dvm644 ${distributionIni} $out/share/tor-browser/distribution/distribution.ini install -Dvm644 ${policiesJson} $out/share/tor-browser/distribution/policies.json runHook postInstall ''; passthru = { inherit sources; updateScript = callPackage ./update.nix { inherit pname version meta; }; }; meta = with lib; { description = "Privacy-focused browser routing traffic through the Tor network"; mainProgram = "tor-browser"; homepage = "https://www.torproject.org/"; changelog = "https://gitweb.torproject.org/builders/tor-browser-build.git/plain/projects/tor-browser/Bundle-Data/Docs/ChangeLog.txt?h=maint-${version}"; platforms = attrNames sources; maintainers = with maintainers; [ felschr panicgh joachifm hax404 ]; # MPL2.0+, GPL+, &c. While it's not entirely clear whether # the compound is "libre" in a strict sense (some components place certain # restrictions on redistribution), it's free enough for our purposes. license = with licenses; [ mpl20 lgpl21Plus lgpl3Plus free ]; sourceProvenance = with sourceTypes; [ binaryNativeCode ]; }; } )