mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 18:18:55 +00:00
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 <ReactorScram@users.noreply.github.com> Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
This commit is contained in:
1
rust/Cargo.lock
generated
1
rust/Cargo.lock
generated
@@ -1989,6 +1989,7 @@ dependencies = [
|
||||
"connlib-shared",
|
||||
"firezone-cli-utils",
|
||||
"keyring",
|
||||
"ring 0.17.7",
|
||||
"secrecy",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -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<wintun::Adapter>,
|
||||
@@ -21,25 +19,24 @@ pub struct Tun {
|
||||
|
||||
impl Tun {
|
||||
pub fn new(config: &InterfaceConfig) -> Result<Self> {
|
||||
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)?;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
@@ -1,22 +0,0 @@
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<!-- Ask Windows to always run us with admin privilege -->
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
@@ -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
|
||||
// <https://stackoverflow.com/questions/59692146/is-it-possible-to-use-the-standard-library-to-spawn-a-process-without-showing-th#60958956>
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ pub enum CliCommands {
|
||||
DebugPipeServer,
|
||||
DebugToken,
|
||||
DebugWintun,
|
||||
Elevated,
|
||||
OpenDeepLink(DeepLink),
|
||||
RegisterDeepLink,
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
39
rust/windows-client/src-tauri/src/client/elevation.rs
Executable file
39
rust/windows-client/src-tauri/src/client/elevation.rs
Executable file
@@ -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<bool, Error> {
|
||||
// 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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
94
rust/windows-client/src-tauri/src/client/wintun_install.rs
Executable file
94
rust/windows-client/src-tauri/src/client/wintun_install.rs
Executable file
@@ -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<DllBytes> {
|
||||
get_platform_dll_bytes()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_platform_dll_bytes() -> Option<DllBytes> {
|
||||
Some(DllBytes {
|
||||
bytes: include_bytes!("../../../wintun/bin/amd64/wintun.dll"),
|
||||
expected_sha256: "e5da8447dc2c320edc0fc52fa01885c103de8c118481f683643cacc3220dafce",
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn get_platform_dll_bytes() -> Option<DllBytes> {
|
||||
// wintun supports aarch64 but it's not in the Firezone repo yet
|
||||
None
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user