From 64f76f5edba56e10e391daa03e77ac52f718967c Mon Sep 17 00:00:00 2001 From: Reactor Scram Date: Mon, 18 Dec 2023 17:54:45 -0600 Subject: [PATCH] feat(windows): Elevate with UAC automatically on startup (#2913) Automatically write the wintun.dll file on startup and then detect whether we need to elevate to admin privileges. I check for privileges by making a test tunnel, so I did #2758 as part of this, which bundles the DLL inside the exe, and then the exe deploys it. --------- Signed-off-by: Reactor Scram Co-authored-by: Jamil --- rust/Cargo.lock | 1 + .../tunnel/src/device_channel/tun_windows.rs | 33 +++---- rust/windows-client/docs/manual_testing.md | 18 +++- rust/windows-client/src-tauri/Cargo.toml | 1 + rust/windows-client/src-tauri/build.rs | 14 +-- .../firezone-windows-client-debug.manifest | 14 --- .../firezone-windows-client-release.manifest | 22 ----- rust/windows-client/src-tauri/src/client.rs | 58 +++++++++++- .../src-tauri/src/client/cli.rs | 1 + .../src-tauri/src/client/debug_commands.rs | 69 ++------------ .../src-tauri/src/client/elevation.rs | 39 ++++++++ .../src-tauri/src/client/gui.rs | 9 +- .../src-tauri/src/client/wintun_install.rs | 94 +++++++++++++++++++ .../src-tauri/src/wintun_install.rs | 28 ------ 14 files changed, 238 insertions(+), 163 deletions(-) delete mode 100755 rust/windows-client/src-tauri/firezone-windows-client-debug.manifest delete mode 100755 rust/windows-client/src-tauri/firezone-windows-client-release.manifest create mode 100755 rust/windows-client/src-tauri/src/client/elevation.rs create mode 100755 rust/windows-client/src-tauri/src/client/wintun_install.rs delete mode 100755 rust/windows-client/src-tauri/src/wintun_install.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 55fe80805..8e7c2799c 100755 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1989,6 +1989,7 @@ dependencies = [ "connlib-shared", "firezone-cli-utils", "keyring", + "ring 0.17.7", "secrecy", "serde", "serde_json", diff --git a/rust/connlib/tunnel/src/device_channel/tun_windows.rs b/rust/connlib/tunnel/src/device_channel/tun_windows.rs index 8bf2ce685..93d836145 100644 --- a/rust/connlib/tunnel/src/device_channel/tun_windows.rs +++ b/rust/connlib/tunnel/src/device_channel/tun_windows.rs @@ -8,8 +8,6 @@ use std::{ }; use tokio::sync::mpsc; -const TUNNEL_UUID: &str = "e9245bc1-b8c1-44ca-ab1d-c6aad4f13b9c"; - // TODO: Make sure all these get dropped gracefully on disconnect pub struct Tun { _adapter: Arc, @@ -21,25 +19,24 @@ pub struct Tun { impl Tun { pub fn new(config: &InterfaceConfig) -> Result { + const TUNNEL_UUID: &str = "e9245bc1-b8c1-44ca-ab1d-c6aad4f13b9c"; + const TUNNEL_NAME: &str = "Firezone Tunnel"; + // The unsafe is here because we're loading a DLL from disk and it has arbitrary C code in it. - // As a defense, we could verify the hash before loading it. This would protect against accidental corruption, but not against attacks. (Because of TOCTOU) + // The Windows client, in `wintun_install` hashes the DLL at startup, before calling connlib, so it's unlikely for the DLL to be accidentally corrupted by the time we get here. let wintun = unsafe { wintun::load_from_path("./wintun.dll") }?; let uuid = uuid::Uuid::from_str(TUNNEL_UUID)?; - let adapter = match wintun::Adapter::create( - &wintun, - "Firezone", - "Firezone Tunnel", - Some(uuid.as_u128()), - ) { - Ok(x) => x, - Err(e) => { - tracing::error!( - "wintun::Adapter::create failed, probably need admin powers: {}", - e - ); - return Err(e.into()); - } - }; + let adapter = + match wintun::Adapter::create(&wintun, "Firezone", TUNNEL_NAME, Some(uuid.as_u128())) { + Ok(x) => x, + Err(e) => { + tracing::error!( + "wintun::Adapter::create failed, probably need admin powers: {}", + e + ); + return Err(e.into()); + } + }; adapter.set_address(config.ipv4)?; diff --git a/rust/windows-client/docs/manual_testing.md b/rust/windows-client/docs/manual_testing.md index 81a164298..e61f41e8b 100755 --- a/rust/windows-client/docs/manual_testing.md +++ b/rust/windows-client/docs/manual_testing.md @@ -15,8 +15,22 @@ If the client stops running while signed in, then the token may be stored in Win # Device ID - [ ] Given the AppData dir for the client doesn't exist, when you run the client, then the client will generate a UUIDv4 (random) and store it in AppData -- [ ] Given the UUID is stored in AppData, when you run the client, then it will load the UUID and compute its SHA256 hash -- [ ] Given the client is running, when a session starts, then the hexadecimal SHA256 hash of the UUID will be used as the device ID +- [ ] Given the UUID is stored in AppData, when you run the client, then it will load the UUID +- [ ] Given the client is running, when a session starts, then the UUID will be used as the device ID + +# DLL + +- [ ] Given wintun.dll does not exist in the same directory as the exe, when you run the exe, then it will create wintun.dll +- [ ] Given wintun.dll has extra bytes appended to the end, when you run the exe, then it will re-write wintun.dll +- [ ] Given wintun.dll does not have the expected SHA256, when you run the exe, then it will re-write wintun.dll +- [ ] Given wintun.dll has the expected SHA256, when you run the exe, then it will not re-write wintun.dll + +# Launching + +- [ ] Given the client is not running, when you open a deep link, then the client will not start +- [ ] Given the client is not running, when you run the exe with normal privileges, then the client will unpack wintun.dll next to the exe if needed, try to start a bogus probe tunnel, and re-launch itself with elevated privilege +- [ ] Given the client is not running, when you run the exe as admin, then the client will unpack wintun.dll next to the exe if needed, try to start a bogus probe tunnel, and keep running +- [ ] Given the client is running, when you open a deep link as part of sign-in, then the client will sign in without a second UAC prompt # Permissions diff --git a/rust/windows-client/src-tauri/Cargo.toml b/rust/windows-client/src-tauri/Cargo.toml index c87c84f73..7dfbbc964 100755 --- a/rust/windows-client/src-tauri/Cargo.toml +++ b/rust/windows-client/src-tauri/Cargo.toml @@ -16,6 +16,7 @@ connlib-client-shared = { workspace = true } connlib-shared = { workspace = true } firezone-cli-utils = { workspace = true } keyring = "2.0.5" +ring = "0.17" secrecy.workspace = true serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/rust/windows-client/src-tauri/build.rs b/rust/windows-client/src-tauri/build.rs index 32bd8a79f..f142e3d55 100755 --- a/rust/windows-client/src-tauri/build.rs +++ b/rust/windows-client/src-tauri/build.rs @@ -1,18 +1,6 @@ fn main() -> anyhow::Result<()> { - let win = tauri_build::WindowsAttributes::new().app_manifest(WINDOWS_MANIFEST); + let win = tauri_build::WindowsAttributes::new(); let attr = tauri_build::Attributes::new().windows_attributes(win); tauri_build::try_build(attr)?; Ok(()) } - -// If we ask for admin privilege in the manifest, we can't run in Cygwin, -// which makes debugging hard on my dev system. -// So always ask for it in Release, which is simpler for users, and in Release -// mode we run as a GUI so we lose stdout/stderr anyway. -// If you need admin privileges for debugging, you can right-click the debug exe. - -#[cfg(debug_assertions)] -const WINDOWS_MANIFEST: &str = include_str!("firezone-windows-client-debug.manifest"); - -#[cfg(not(debug_assertions))] -const WINDOWS_MANIFEST: &str = include_str!("firezone-windows-client-release.manifest"); diff --git a/rust/windows-client/src-tauri/firezone-windows-client-debug.manifest b/rust/windows-client/src-tauri/firezone-windows-client-debug.manifest deleted file mode 100755 index 1f5498f0b..000000000 --- a/rust/windows-client/src-tauri/firezone-windows-client-debug.manifest +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/rust/windows-client/src-tauri/firezone-windows-client-release.manifest b/rust/windows-client/src-tauri/firezone-windows-client-release.manifest deleted file mode 100755 index 317a9094c..000000000 --- a/rust/windows-client/src-tauri/firezone-windows-client-release.manifest +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/rust/windows-client/src-tauri/src/client.rs b/rust/windows-client/src-tauri/src/client.rs index cf66341e6..4b66ffc3a 100644 --- a/rust/windows-client/src-tauri/src/client.rs +++ b/rust/windows-client/src-tauri/src/client.rs @@ -1,18 +1,23 @@ use anyhow::Result; use clap::Parser; use cli::CliCommands as Cmd; +use std::{os::windows::process::CommandExt, process::Command}; mod cli; mod debug_commands; mod deep_link; mod device_id; +mod elevation; mod gui; mod logging; mod settings; +mod wintun_install; /// Prevents a problem where changing the args to `gui::run` breaks static analysis on non-Windows targets, where the gui is stubbed out #[allow(dead_code)] pub(crate) struct GuiParams { + /// True if we were re-launched with elevated permissions. If the user launched us directly with elevated permissions, this is false. + flag_elevated: bool, /// True if we should slow down I/O operations to test how the GUI handles slow I/O inject_faults: bool, } @@ -21,13 +26,41 @@ pub(crate) struct GuiParams { /// `C:/Users/$USER/AppData/Local/dev.firezone.client` pub(crate) struct AppLocalDataDir(std::path::PathBuf); +// Hides Powershell's console on Windows +// +const CREATE_NO_WINDOW: u32 = 0x08000000; + pub(crate) fn run() -> Result<()> { let cli = cli::Cli::parse(); match cli.command { - None => gui::run(GuiParams { - inject_faults: cli.inject_faults, - }), + None => { + if elevation::check()? { + // We're already elevated, just run the GUI + gui::run(GuiParams { + flag_elevated: false, + inject_faults: cli.inject_faults, + }) + } else { + // We're not elevated, ask Powershell to re-launch us, then exit + let current_exe = tauri_utils::platform::current_exe()?; + if current_exe.display().to_string().contains('\"') { + anyhow::bail!("The exe path must not contain double quotes, it makes it hard to elevate with Powershell"); + } + Command::new("powershell") + .creation_flags(CREATE_NO_WINDOW) + .arg("-Command") + .arg("Start-Process") + .arg("-FilePath") + .arg(format!(r#""{}""#, current_exe.display())) + .arg("-Verb") + .arg("RunAs") + .arg("-ArgumentList") + .arg("elevated") + .spawn()?; + Ok(()) + } + } Some(Cmd::Debug) => { println!("debug"); Ok(()) @@ -35,7 +68,26 @@ pub(crate) fn run() -> Result<()> { Some(Cmd::DebugPipeServer) => debug_commands::pipe_server(), Some(Cmd::DebugToken) => debug_commands::token(), Some(Cmd::DebugWintun) => debug_commands::wintun(cli), + // If we already tried to elevate ourselves, don't try again + Some(Cmd::Elevated) => gui::run(GuiParams { + flag_elevated: true, + inject_faults: cli.inject_faults, + }), Some(Cmd::OpenDeepLink(deep_link)) => debug_commands::open_deep_link(&deep_link.url), Some(Cmd::RegisterDeepLink) => debug_commands::register_deep_link(), } } + +#[cfg(test)] +mod tests { + use anyhow::Result; + + #[test] + fn exe_path() -> Result<()> { + // e.g. `\\\\?\\C:\\cygwin64\\home\\User\\projects\\firezone\\rust\\target\\debug\\deps\\firezone_windows_client-5f44800b2dafef90.exe` + let path = tauri_utils::platform::current_exe()?.display().to_string(); + assert!(path.contains("target")); + assert!(!path.contains('\"'), "`{}`", path); + Ok(()) + } +} diff --git a/rust/windows-client/src-tauri/src/client/cli.rs b/rust/windows-client/src-tauri/src/client/cli.rs index 562e407c1..e0d2f896e 100755 --- a/rust/windows-client/src-tauri/src/client/cli.rs +++ b/rust/windows-client/src-tauri/src/client/cli.rs @@ -15,6 +15,7 @@ pub enum CliCommands { DebugPipeServer, DebugToken, DebugWintun, + Elevated, OpenDeepLink(DeepLink), RegisterDeepLink, } diff --git a/rust/windows-client/src-tauri/src/client/debug_commands.rs b/rust/windows-client/src-tauri/src/client/debug_commands.rs index a3b6a7e37..92534fda1 100644 --- a/rust/windows-client/src-tauri/src/client/debug_commands.rs +++ b/rust/windows-client/src-tauri/src/client/debug_commands.rs @@ -4,7 +4,6 @@ use crate::client::cli::Cli; use anyhow::Result; use keyring::Entry; -use std::sync::Arc; use tokio::runtime::Runtime; // TODO: In tauri-plugin-deep-link, this is the identifier in tauri.conf.json @@ -35,10 +34,7 @@ pub fn token() -> Result<()> { } pub fn open_deep_link(path: &url::Url) -> Result<()> { - let subscriber = tracing_subscriber::FmtSubscriber::builder() - .with_max_level(tracing::Level::TRACE) - .finish(); - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + tracing_subscriber::fmt::init(); let rt = Runtime::new()?; rt.block_on(crate::client::deep_link::open(PIPE_NAME, path))?; @@ -49,10 +45,7 @@ pub fn open_deep_link(path: &url::Url) -> Result<()> { // although I believe it's considered best practice on Windows to use named pipes for // single-instance apps. pub fn pipe_server() -> Result<()> { - let subscriber = tracing_subscriber::FmtSubscriber::builder() - .with_max_level(tracing::Level::TRACE) - .finish(); - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + tracing_subscriber::fmt::init(); let rt = Runtime::new()?; rt.block_on(async { @@ -71,58 +64,12 @@ pub fn register_deep_link() -> Result<()> { } pub fn wintun(_: Cli) -> Result<()> { - for _ in 0..3 { - println!("Creating adapter..."); - test_wintun_once()?; + tracing_subscriber::fmt::init(); + + if crate::client::elevation::check()? { + tracing::info!("Elevated"); + } else { + tracing::warn!("Not elevated") } Ok(()) } - -fn test_wintun_once() -> Result<()> { - //Must be run as Administrator because we create network adapters - //Load the wintun dll file so that we can call the underlying C functions - //Unsafe because we are loading an arbitrary dll file - let wintun = unsafe { wintun::load_from_path("./wintun.dll") }?; - - //Try to open an adapter with the name "Demo" - let adapter = match wintun::Adapter::open(&wintun, "Demo") { - Ok(a) => a, - Err(_) => { - //If loading failed (most likely it didn't exist), create a new one - wintun::Adapter::create(&wintun, "Demo", "Example manor hatch stash", None)? - } - }; - //Specify the size of the ring buffer the wintun driver should use. - let session = Arc::new(adapter.start_session(wintun::MAX_RING_CAPACITY)?); - - //Get a 20 byte packet from the ring buffer - let mut packet = session.allocate_send_packet(20)?; - let bytes: &mut [u8] = packet.bytes_mut(); - //Write IPV4 version and header length - bytes[0] = 0x40; - - //Finish writing IP header - bytes[9] = 0x69; - bytes[10] = 0x04; - bytes[11] = 0x20; - //... - - //Send the packet to wintun virtual adapter for processing by the system - session.send_packet(packet); - - // Sleep for a few seconds in case we want to confirm the adapter shows up in Device Manager. - std::thread::sleep(std::time::Duration::from_secs(5)); - - //Stop any readers blocking for data on other threads - //Only needed when a blocking reader is preventing shutdown Ie. it holds an Arc to the - //session, blocking it from being dropped - session.shutdown()?; - - //the session is stopped on drop - //drop(session); - - //drop(adapter) - //And the adapter closes its resources when dropped - - Ok(()) -} diff --git a/rust/windows-client/src-tauri/src/client/elevation.rs b/rust/windows-client/src-tauri/src/client/elevation.rs new file mode 100755 index 000000000..fc3a9cc8c --- /dev/null +++ b/rust/windows-client/src-tauri/src/client/elevation.rs @@ -0,0 +1,39 @@ +use crate::client::wintun_install; +use std::str::FromStr; + +#[derive(thiserror::Error, Debug)] +pub(crate) enum Error { + #[error("couldn't install wintun.dll")] + DllInstall(#[from] wintun_install::Error), + #[error("couldn't load wintun.dll")] + DllLoad, + #[error("UUID parse error - This should be impossible since the UUID is hard-coded")] + Uuid, +} + +/// Creates a bogus wintun tunnel to check whether we have permissions to create wintun tunnels. +/// Extracts wintun.dll if needed. +/// +/// Returns true if already elevated, false if not elevated, error if we can't be sure +pub(crate) fn check() -> Result { + // Almost the same as the code in tun_windows.rs in connlib + const TUNNEL_UUID: &str = "72228ef4-cb84-4ca5-a4e6-3f8636e75757"; + const TUNNEL_NAME: &str = "Firezone Elevation Check"; + + match wintun_install::ensure_dll() { + Ok(_) => {} + Err(wintun_install::Error::PermissionDenied) => return Ok(false), + Err(e) => return Err(Error::DllInstall(e)), + } + + // The unsafe is here because we're loading a DLL from disk and it has arbitrary C code in it. + // TODO: As a defense, we could verify the hash before loading it. This would protect against accidental corruption, but not against attacks. (Because of TOCTOU) + let wintun = unsafe { wintun::load_from_path("./wintun.dll") }.map_err(|_| Error::DllLoad)?; + let uuid = uuid::Uuid::from_str(TUNNEL_UUID).map_err(|_| Error::Uuid)?; + + // Wintun hides the exact Windows error, so let's assume the only way Adapter::create can fail is if we're not elevated. + if wintun::Adapter::create(&wintun, "Firezone", TUNNEL_NAME, Some(uuid.as_u128())).is_err() { + return Ok(false); + } + Ok(true) +} diff --git a/rust/windows-client/src-tauri/src/client/gui.rs b/rust/windows-client/src-tauri/src/client/gui.rs index 98d4c175e..40d1bbb8a 100755 --- a/rust/windows-client/src-tauri/src/client/gui.rs +++ b/rust/windows-client/src-tauri/src/client/gui.rs @@ -41,7 +41,10 @@ const TAURI_ID: &str = "dev.firezone.client"; /// Runs the Tauri GUI and returns on exit or unrecoverable error pub(crate) fn run(params: client::GuiParams) -> Result<()> { - let client::GuiParams { inject_faults } = params; + let client::GuiParams { + flag_elevated, + inject_faults, + } = params; // Needed for the deep link server let rt = tokio::runtime::Runtime::new()?; @@ -100,7 +103,7 @@ pub(crate) fn run(params: client::GuiParams) -> Result<()> { } } }) - .setup(|app| { + .setup(move |app| { // Change to data dir so the file logger will write there and not in System32 if we're launching from an app link let cwd = app_local_data_dir(&app.handle())?.0.join("data"); std::fs::create_dir_all(&cwd)?; @@ -114,6 +117,8 @@ pub(crate) fn run(params: client::GuiParams) -> Result<()> { // It's hard to set it up before Tauri's setup, because Tauri knows where all the config and data go in AppData and I don't want to replicate their logic. let logging_handles = client::logging::setup(&advanced_settings.log_filter)?; tracing::info!("started log"); + // I checked this on my dev system to make sure Powershell is doing what I expect and passing the argument back to us after relaunch + tracing::debug!("flag_elevated: {flag_elevated}"); let app_handle = app.handle(); let _ctlr_task = tokio::spawn(async move { diff --git a/rust/windows-client/src-tauri/src/client/wintun_install.rs b/rust/windows-client/src-tauri/src/client/wintun_install.rs new file mode 100755 index 000000000..e4de310f8 --- /dev/null +++ b/rust/windows-client/src-tauri/src/client/wintun_install.rs @@ -0,0 +1,94 @@ +//! "Installs" wintun.dll at runtime by copying it into whatever folder the exe is in + +use ring::digest; +use std::{ + fs, + io::{self, Read}, + path::Path, +}; + +struct DllBytes { + /// Bytes embedded in the client with `include_bytes` + bytes: &'static [u8], + /// Expected SHA256 hash + expected_sha256: &'static str, +} + +#[derive(thiserror::Error, Debug)] +pub(crate) enum Error { + #[error("current exe path unknown")] + CurrentExePathUnknown, + #[error("permission denied")] + PermissionDenied, + #[error("platform not supported")] + PlatformNotSupported, + #[error("write failed: `{0:?}`")] + WriteFailed(io::Error), +} + +/// Installs the DLL alongside the current exe, if needed +/// The reason not to do it in the current working dir is that deep links may launch +/// with a current working dir of `C:\Windows\System32` +/// The reason not to do it in AppData is that learning our AppData path before Tauri +/// setup is difficult. +/// The reason not to do it in `C:\Program Files` is that running in portable mode +/// is useful for development, even though it's not supported for production. +pub(crate) fn ensure_dll() -> Result<(), Error> { + let dll_bytes = get_dll_bytes().ok_or(Error::PlatformNotSupported)?; + + let path = tauri_utils::platform::current_exe() + .map_err(|_| Error::CurrentExePathUnknown)? + .with_file_name("wintun.dll"); + tracing::debug!("wintun.dll path = {path:?}"); + + // This hash check is not meant to protect against attacks. It only lets us skip redundant disk writes, and it updates the DLL if needed. + if !dll_already_exists(&path, &dll_bytes) { + fs::write(&path, dll_bytes.bytes).map_err(|e| match e.kind() { + io::ErrorKind::PermissionDenied => Error::PermissionDenied, + _ => Error::WriteFailed(e), + })?; + } + Ok(()) +} + +fn dll_already_exists(path: &Path, dll_bytes: &DllBytes) -> bool { + let mut f = match fs::File::open(path) { + Err(_) => return false, + Ok(x) => x, + }; + + let actual_len = usize::try_from(f.metadata().unwrap().len()).unwrap(); + let expected_len = dll_bytes.bytes.len(); + // If the dll is 100 MB instead of 0.5 MB, this allows us to skip a 100 MB read + if actual_len != expected_len { + return false; + } + + let mut buf = vec![0u8; expected_len]; + if f.read_exact(&mut buf).is_err() { + return false; + } + + let expected = ring::test::from_hex(dll_bytes.expected_sha256).unwrap(); + let actual = digest::digest(&digest::SHA256, &buf); + expected == actual.as_ref() +} + +/// Returns the platform-specific bytes of wintun.dll, or None if we don't support the compiled platform. +fn get_dll_bytes() -> Option { + get_platform_dll_bytes() +} + +#[cfg(target_arch = "x86_64")] +fn get_platform_dll_bytes() -> Option { + Some(DllBytes { + bytes: include_bytes!("../../../wintun/bin/amd64/wintun.dll"), + expected_sha256: "e5da8447dc2c320edc0fc52fa01885c103de8c118481f683643cacc3220dafce", + }) +} + +#[cfg(target_arch = "aarch64")] +fn get_platform_dll_bytes() -> Option { + // wintun supports aarch64 but it's not in the Firezone repo yet + None +} diff --git a/rust/windows-client/src-tauri/src/wintun_install.rs b/rust/windows-client/src-tauri/src/wintun_install.rs deleted file mode 100755 index f07322f60..000000000 --- a/rust/windows-client/src-tauri/src/wintun_install.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! "Installs" wintun.dll at runtime by copying it into whatever folder the exe is in - -pub(crate) struct _DllBytes { - /// Bytes embedded in the client with `include_bytes` - bytes: &'static [u8], - /// Expected SHA256 hash - expected_sha256: &'static str, -} - -/// Returns the platform-specific bytes of wintun.dll, or None if we don't support the compiled platform. -pub(crate) fn _get_dll_bytes() -> Option<_DllBytes> { - _get_platform_dll_bytes() -} - -#[cfg(target_arch = "x86_64")] -fn _get_platform_dll_bytes() -> Option<_DllBytes> { - // SHA256 e5da8447dc2c320edc0fc52fa01885c103de8c118481f683643cacc3220dafce - Some(_DllBytes { - bytes: include_bytes!("../../wintun/bin/amd64/wintun.dll"), - expected_sha256: "e5da8447dc2c320edc0fc52fa01885c103de8c118481f683643cacc3220dafce", - }) -} - -#[cfg(target_arch = "aarch64")] -fn _get_platform_dll_bytes() -> Option<&'static [u8]> { - // wintun supports aarch64 but it's not in the Firezone repo yet - None -}