diff --git a/Cargo.toml b/Cargo.toml index 92cd578..850c42d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ ] exclude = [ - "keysas-usbfilter" + "keysas-firewall" ] [patch.crates-io.loopdev] diff --git a/README.md b/README.md index 955c7c1..441aca1 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@ # USB virus cleaning station # Main features + - Retrieve untrusted files from USB (via keysas-io) or over the network - Perform multiple checks - - Run anti-virus check (ClamAV) - - Run Yara parsing - - Run extensions and size checks + - Run anti-virus check (ClamAV) + - Run Yara parsing + - Run extensions and size checks - Signatures (Files and USB keys) - Trusted (Outgoing) USB device must be signed with Keysas-admin app - Each verified file signature is stored in the corresponding file report @@ -17,9 +18,10 @@ - Private keys are stored using PKCS#8 format - x509 certificates are signed by the internal PKI (using Keysas-admin) - Authentication - - Users can be authenticated using personal Yubikeys 5 + - Users can be authenticated using personal Yubikeys 5 # Keysas-core + ## Architecture
@@ -34,6 +36,7 @@ Files are passed between daemons as raw file descriptors and using abstract sock - Daemons are sandboxed using Seccomp (x86_64 & aarch64) ## Other binaries or applications available + - Keysas-io: Daemon watching udev events to verify the signature of any mass storage USB devices and mount it as a IN (no or invalid signature) or OUT device (valid signature). - Keysas-sign: Command line utility to import PEM certificate via Keysas-admin - Keysas-fido: Command line utility to manage Yubikeys 5 enrollment @@ -43,8 +46,9 @@ Files are passed between daemons as raw file descriptors and using abstract sock ## Installation -On Debian stable (Bookwoom): -``` +On Debian stable (Bookwoom only): + +```bash apt -qy install -y libyara-dev libyara9 wget cmake make lsb-release software-properties-common libseccomp-dev clamav-daemon clamav-freshclam pkg-config git bash libudev-dev libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly -y @@ -54,7 +58,7 @@ make help make build make install ``` + ## User documentation User documentation can be found here : [https://keysas.fr](https://keysas.fr) - diff --git a/documentation/user_documentation/conf.py b/documentation/user_documentation/conf.py index 79b4cab..f39b1c1 100644 --- a/documentation/user_documentation/conf.py +++ b/documentation/user_documentation/conf.py @@ -24,9 +24,10 @@ author = 'Stephane N' # The short X.Y version -version = '2.0' +version = '2.1' # The full version, including alpha/beta/rc tags -release = 'v2.0' +release = 'v2.1' + # -- General configuration --------------------------------------------------- diff --git a/documentation/user_documentation/index.rst b/documentation/user_documentation/index.rst index f88b3a9..2ad8d21 100644 --- a/documentation/user_documentation/index.rst +++ b/documentation/user_documentation/index.rst @@ -35,5 +35,7 @@ User documentation networkgw raspberry keysas-admin + windows_firewall + diff --git a/documentation/user_documentation/installation.rst b/documentation/user_documentation/installation.rst index 192a2e4..e993342 100644 --- a/documentation/user_documentation/installation.rst +++ b/documentation/user_documentation/installation.rst @@ -38,8 +38,9 @@ Getting **Keysas** ------------------- A pre-compiled **Keysas** binary is at your -disposal, you can choose and download a specific version of **Keysas** -using the :ref:`download section `. +disposal. We recommend using the latest version here: +https://github.com/r3dlight/keysas/tags + Download the following files of lastest stable version. * keysas-vx.y.z.zip diff --git a/documentation/user_documentation/raspberry.rst b/documentation/user_documentation/raspberry.rst index f48fee6..b6fd979 100644 --- a/documentation/user_documentation/raspberry.rst +++ b/documentation/user_documentation/raspberry.rst @@ -21,8 +21,9 @@ The code is entirely written in Rust, sandboxed, and follows the principle of le Download ========= -- `keysas-sd-v2.0 `_ (`sha256 `_) -- `keysas-admin-v2.0 (GNU/Linux) `_ (`sha256 `_) +- `keysas-sd-v2.1 `_ (`sha256 `_) +- `keysas-admin-v2.1 (GNU/Linux) `_ (`sha256 `_) + The downloaded image will automatically resize according to the size of your MicroSD card. To copy the **Keysas** station image to your SD card: diff --git a/documentation/user_documentation/windows_firewall.rst b/documentation/user_documentation/windows_firewall.rst new file mode 100644 index 0000000..f232871 --- /dev/null +++ b/documentation/user_documentation/windows_firewall.rst @@ -0,0 +1,63 @@ +******************** +Windows USB firewall +******************** + +**Keysas** system also includes a **USB firewall** for Windows in order to check that: +- USB stick plugged on user laptop have been checked by a Keysas station; +- Files on the USB stick have been validated by the station. + +.. warning:: + **USB firewall** has only been tested on Windows 10 laptop in debug mode for now. + +Architecture +============ + +The firewall is composed of four elements: + +- In kernel space + - A USB bus filter driver + - A minifilter (driver to filter system calls towards the filesystem) +- In userspace + - A daemon supervising the two drivers and checks files and reports based on the system security policy + - A tray application to allow the end user to control the security settings + +Security Policy configuration +============================= + +System security policy is configured from a TOML file at the base of the Daemon directory. +The policy is configured with: + +- 'disable_unsigned_usb': if set to 'true', unsigned usb devices are allowed. No checks are performed on files on these devices. +- 'allow_user_usb_authorization': if set to 'true', grant the user the ability to manually allow unsigned USB devices. No checks are performed on files on these devices. +- 'allow_user_file_read': if set to 'true', grant the user the ability to manually allow read access to an unsigned file. +- 'allow_user_file_write': if set to 'true', grant the user the ability to manually allow write access to file on a USB device. 'allow_user_file_read' must also be set to true. + +If parameters are missing from the configuration file, they are considered to be set to 'false'. + +CA certificates must be provided to the daemon. The path to the pem files is given as arguments to the command line. + +The complete command line is + +```bash +./keysas-usbfilter-daemon.exe -config -ca_cl -ca_pq +``` + +Installation +============ + +Driver compilation +------------------ + +The drivers have been tested on a Windows 10 laptop in debug mode (unsigned driver allowed). +They have been compiled with Microsoft Visual Studio 2022 with SDK and WDK version 10.0.22621.0. + +Service and application compilation +----------------------------------- + +The Keysas daemon and tray application have been compiled and tested on Windows 10 with the following dependencies: + +- Rust toolchain: for example +- Clang toolchain: for example +- CMake: +- Tauri: +- Npm: for example \ No newline at end of file diff --git a/keysas-admin/src-tauri/Cargo.toml b/keysas-admin/src-tauri/Cargo.toml index 843e231..63c15c5 100644 --- a/keysas-admin/src-tauri/Cargo.toml +++ b/keysas-admin/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keysas-admin" -version = "2.0.0" +version = "2.1.0" description = "Keysas stations administration application" authors = ["Stephane N", "Luc Bonnafoux"] license = "GPL-3.0" diff --git a/keysas-backend/Cargo.toml b/keysas-backend/Cargo.toml index d33ab1a..33b36b7 100644 --- a/keysas-backend/Cargo.toml +++ b/keysas-backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keysas-backend" -version = "2.0.0" +version = "2.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/keysas-core/Cargo.toml b/keysas-core/Cargo.toml index 5d00667..9ccb694 100644 --- a/keysas-core/Cargo.toml +++ b/keysas-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keysas-core" -version = "2.0.0" +version = "2.1.0" edition = "2021" [dependencies] diff --git a/keysas-core/debian/keysas-transit.security b/keysas-core/debian/keysas-transit.security index c6c671d..dac2004 100644 --- a/keysas-core/debian/keysas-transit.security +++ b/keysas-core/debian/keysas-transit.security @@ -28,7 +28,6 @@ MemoryDenyWriteExecute=yes TemporaryFileSystem=/etc BindReadOnlyPaths=/etc/keysas TemporaryFileSystem=/var -BindPaths=/var/local/transit IPAddressDeny=any RestrictAddressFamilies=AF_INET AF_UNIX IPAddressAllow=127.0.0.1/8 diff --git a/keysas-firewall/README.md b/keysas-firewall/README.md new file mode 100644 index 0000000..9841565 --- /dev/null +++ b/keysas-firewall/README.md @@ -0,0 +1,78 @@ +# Keysas USB firewall + +The keysas USB firewall is used on Windows client to control that: + +- USB devices connected have been enrolled in the system +- Files on USB devices have been validated by a Keysas station + +## Architecture + +The firewall is composed of four elements: + +- In kernel space + - A USB bus filter driver + - A minifilter (driver to filter system calls towards the filesystem) +- In userspace + - A daemon that supervises the two drivers and checks files and reports based on the system security policy + - A tray application to allow the end user to control the security settings + +## Security Policy configuration + +System security policy is configured from a TOML file at the base of the Daemon directory. +The policy is configured with: + +- 'disable_unsigned_usb': if set to 'true' unsigned usb devices are allowed. No checks are performed on files on these devices. +- 'allow_user_usb_authorization': if set to 'true' grant the user the ability to manually allow unsigned USB devices. No checks are performed on files on these devices. +- 'allow_user_file_read': if set to 'true' grant the user the ability to manually allow read access to an unsigned file. +- 'allow_user_file_write': if set to 'true' grant the user the ability to manually allow write access to file on a USB device. 'allow_user_file_read' must also be set to true. + +If parameters are missing from the configuration file they are considered to be set to 'false'. + +CA certificates must be provided to the daemon. The path to the pem files is given as arguments to the command line. + +The comple command line is + +```bash +./keysas-usbfilter-daemon.exe -config -ca_cl -ca_pq +``` + +## TODO List + + This firewall is still a work in progress. + +- USB bus filter driver + - [ ] Bus call interception +- Minifilter + - [X] System call interception and filtering + - [X] Track per file context + - [X] Allow authorization changes + - [X] Filter file open and create operations + - [X] Filter write operation + - [ ] Clean code: check IRQL, check paging, check fastIO, check sparse file API, check all flags in the pre-op filters... +- Daemon + - [X] Check report and files + - [X] Use CA certificate to check report certificate + - [X] Enforce system security policy + - [ ] Check USB devices +- Tray app + - [X] Display files + - [~] Display USB devices + - [X] Allow authorization changes + - [X] Add drop down menu for authorization selection + +## Installation + +### Driver compilation + +The drivers have been tested on a Windows 10 laptop in debug mode (unsigned driver allowed). +They have been compiled with Microsoft Visual Studio 2022 with SDK and WDK version 10.0.22621.0. + +### Service and application compilation + +The Keysas daemon and tray application have been compiled and tested on Windows 10 with the following dependencies: + +- Rust toolchain: for example +- Clang toolchain: for example +- CMake: +- Tauri: +- Npm: for example diff --git a/keysas-firewall/daemon/Cargo.toml b/keysas-firewall/daemon/Cargo.toml index aaf3d78..ad8b1ef 100644 --- a/keysas-firewall/daemon/Cargo.toml +++ b/keysas-firewall/daemon/Cargo.toml @@ -19,6 +19,13 @@ wchar = "0.11" mbrman = "0.5" libc = "0.2" keysas_lib = { path = "../../keysas_lib" } +serde = "1.0" +serde_json = "1.0" +serde_derive = "1.0" +clap = { version = "4", default-features = false, features = ["std", "cargo"] } +toml = "0.7" +libmailslot = {path = "../libmailslot"} +x509-cert = "0.2" [dependencies.windows] version = "0.48.0" diff --git a/keysas-firewall/daemon/src/controller.rs b/keysas-firewall/daemon/src/controller.rs new file mode 100644 index 0000000..536f2ea --- /dev/null +++ b/keysas-firewall/daemon/src/controller.rs @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * + * (C) Copyright 2019-2023 Luc Bonnafoux, Stephane Neveu + * + */ + +//! Service controller + +#![warn(unused_extern_crates)] +#![forbid(non_shorthand_field_patterns)] +#![warn(dead_code)] +#![warn(missing_debug_implementations)] +#![warn(missing_copy_implementations)] +#![warn(trivial_numeric_casts)] +#![warn(unused_extern_crates)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] +#![warn(variant_size_differences)] +#![forbid(private_in_public)] +#![warn(overflowing_literals)] +#![warn(deprecated)] +#![warn(unused_imports)] + +use libc::c_void; +use std::mem::size_of; +use std::path::PathBuf; +use std::path::{Component, Path}; +use std::ffi::OsStr; +use std::sync::Arc; +use std::fs; +use windows::core::PCSTR; +use windows::s; +use windows::Win32::Foundation::GetLastError; +use windows::Win32::Storage::FileSystem::{ + CreateFileA, ReadFile, FILE_FLAG_BACKUP_SEMANTICS, FILE_SHARE_READ, FILE_SHARE_WRITE, + IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, OPEN_EXISTING, +}; +use windows::Win32::System::Ioctl::VOLUME_DISK_EXTENTS; +use windows::Win32::System::IO::DeviceIoControl; +use windows::Win32::UI::WindowsAndMessaging::*; +use anyhow::anyhow; +use serde::Deserialize; +use x509_cert::Certificate; +use x509_cert::der::DecodePem; + +use keysas_lib::file_report::parse_report; +use crate::driver_interface::{WindowsDriverInterface, KeysasFilterOperation, KeysasAuthorization}; +use crate::tray_interface; +use crate::Config; + +#[derive(Debug, Deserialize, Clone, Copy)] +struct SecurityPolicy { + disable_unsigned_usb: bool, + allow_user_usb_authorization: bool, + allow_user_file_read: bool, + allow_user_file_write: bool, +} + +impl Default for SecurityPolicy { + fn default() -> Self { + Self { + disable_unsigned_usb: false, + allow_user_usb_authorization: false, + allow_user_file_read: false, + allow_user_file_write: false, + } + } +} + +/// Service controller object, it contains handles to the service communication interfaces and data +#[derive(Debug, Clone)] +pub struct ServiceController { + driver_if: WindowsDriverInterface, + policy: SecurityPolicy, + ca_cert_cl: Certificate, + ca_cert_pq: Certificate +} + +impl ServiceController { + /// Initialize the service controller + pub fn init(config: &Config) -> Result, anyhow::Error> { + if !cfg!(windows) { + log::error!("OS not supported"); + return Err(anyhow!("Failed to open driver interface: OS not supported")); + } + + // Load administration security policy + let config_toml = match fs::read_to_string(&config.config) { + Ok(s) => s, + Err(e) => { + log::error!("Failed to read configuration file {e}"); + return Err(anyhow!("Failed to open driver interface: Failed to read configuration file {e}")); + } + }; + + let policy: SecurityPolicy = match toml::from_str(&config_toml) { + Ok(p) => p, + Err(e) => { + log::error!("Failed to parse configuration file {e}"); + return Err(anyhow!("Failed to open driver interface: Failed to parse configuration file {e}")); + } + }; + + // Load local certificates for the CA + let cl_cert_pem = fs::read_to_string(&config.ca_cert_cl)?; + let ca_cert_cl = Certificate::from_pem(cl_cert_pem)?; + + let pq_cert_pem = fs::read_to_string(&config.ca_cert_pq)?; + let ca_cert_pq = Certificate::from_pem(pq_cert_pem)?; + + + // Start the interface with the kernel driver + let driver_if = WindowsDriverInterface::open_driver_com()?; + + // Initialize the controller + let ctrl = Arc::new(ServiceController { + driver_if, + policy, + ca_cert_cl, + ca_cert_pq + }); + + driver_if.start_driver_com(&ctrl)?; + + // Start the interface with the HMI + if let Err(e) = tray_interface::init(&ctrl) { + log::error!("Failed to start tray interface server: {e}"); + return Err(anyhow!("Failed to start tray interface server")); + }; + + Ok(ctrl) + } + + /// Handle requests coming from the driver + /// Return the authorization state for the USB device or the file, or an error + /// + /// # Arguments + /// + /// * 'operation' - Operation code + /// * 'content' - Content of the request + pub fn handle_driver_request(&self, operation: KeysasFilterOperation, + content: &[u16]) -> Result { + // Dispatch the request + let result = match operation { + KeysasFilterOperation::ScanFile => { + match self.authorize_file(operation, &content) { + Ok((result, true)) => { + // Send the authorization result to the tray interface + if let Err(e) = tray_interface::send_file_auth_status( + &content, result) { + println!("Failed to send file status to tray app {e}"); + } + result + }, + Ok((result, false)) => result, + Err(e) => { + println!("Failed to validate the file: {e}"); + KeysasAuthorization::AuthBlock + } + } + } + KeysasFilterOperation::ScanUsb => KeysasAuthorization::AuthAllowAll, // For now, allow all + }; + + Ok(result) + } + + /// Handle a request coming from the HMI + pub fn handle_tray_request(&self, req: &tray_interface::FileUpdateMessage) + -> Result<(), anyhow::Error> { + // Check that the request is conforme to the security policy + if (KeysasAuthorization::AuthAllowRead == req.authorization) + && !self.policy.allow_user_file_read { + println!("Authorization change not allowed"); + return Err(anyhow!("Authorization change not allowed")); + } + + if (KeysasAuthorization::AuthAllowAll == req.authorization) + && (!self.policy.allow_user_file_read + || !self.policy.allow_user_file_write) { + println!("Authorization change not allowed"); + return Err(anyhow!("Authorization change not allowed")); + } + + // Create the request for the driver + // The format is : + // - FileID: 32 bytes + // - New authorization: 1 byte + let mut request: [u8; 33] = [0; 33]; + let mut index = 0; + + for db in req.id { + let bytes = db.to_ne_bytes(); + request[index] = bytes[0]; + request[index+1] = bytes[1]; + index += 2; + } + request[32] = req.authorization.as_u8(); + + // Send the request to the driver + if let Err(e) = self.driver_if.send_msg(&request) { + println!("Failed to pass tray request to driver {e}"); + return Err(anyhow!("Failed to pass tray request to driver {e}")); + } + + Ok(()) + } + + /// Check a USB device to allow it not + /// Return Ok(true) or Ok(false) according to the authorization + /// + /// # Arguments + /// + /// * 'content' - Content of the request from the driver + fn authorize_usb(&self, content: &[u16]) -> Result { + println!("Received USB scan request: {:?}", content); + let mut buffer: [u8; 4096] = [0; 4096]; + let mut byte_read: u32 = 0; + + // Open the device on the first sector + let device = unsafe { + match CreateFileA( + s!("\\\\.\\D:"), + 1179785u32, + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + None, + ) { + Ok(d) => d, + Err(_) => { + println!("Failed to open device"); + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("Failed to open device")); + } + } + }; + + if device.is_invalid() { + println!("Invalid device handle"); + return Err(anyhow!("Invalid device handle")); + } + + let mut vde = VOLUME_DISK_EXTENTS::default(); + let mut dw: u32 = 0; + match unsafe { + DeviceIoControl( + device, + IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + None, + 0, + Some(&mut vde as *mut _ as *mut c_void), + u32::try_from(size_of::())?, + Some(&mut dw), + None, + ).as_bool() + } { + true => (), + false => { + println!("Failed to query device"); + return Err(anyhow!("Failed to query device")); + + } + } + + let mut drive_path = String::from("\\\\.\\PhysicalDrive"); + drive_path.push_str(&vde.Extents[0].DiskNumber.to_string()); + + println!("Physical Drive path: {:?}", drive_path); + + let drive_str = PCSTR::from_raw(drive_path.as_ptr() as *const u8); + unsafe { + println!("Physical Drive path windows: {:?}", drive_str.to_string()?); + } + + let handle_usb = unsafe { + match CreateFileA( + drive_str, + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + None, + ) { + Ok(d) => d, + Err(_) => { + println!("Failed to open usb"); + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("Failed to open usb")); + } + } + }; + + if handle_usb.is_invalid() { + println!("Invalid device usb handle"); + return Err(anyhow!("Invalid device usb handle")); + } + + // Move the file pointer after the MBR table (512B) + // and read the signature content + let read = unsafe { + //SetFilePointer(device, 512, None, FILE_BEGIN); + ReadFile( + handle_usb, + Some(buffer.as_mut_ptr() as *mut c_void), + 4096, + Some(&mut byte_read), + None, + ) + }; + + if read.as_bool() { + println!("Device content: {:?}", buffer); + } else { + println!("Failed to read device content"); + unsafe { + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + } + } + + Ok(true) + } + + /// Decide to authorize a file + /// Start by whitelisting file that belongs to Windows and remove directories + /// Then try to validate it with a station report + /// Finaly if it fails ask the user to validate it manualy + /// + /// USB_op will be used to apply a device wide filter policy + /// + /// Returns a tuple containing + /// - if the file is authorized or not + /// - if a notification must be sent to the user or not + /// + /// # Arguments + /// + /// * 'usb_op' - Device wide filtering policy + /// * 'content' - Content of the driver request, it contains the path to the file + fn authorize_file(&self, _usb_op: KeysasFilterOperation, content: &[u16]) + -> Result<(KeysasAuthorization, bool), anyhow::Error> { + // Extract content of the request + // The first 32 bytes are the File ID + let file_id = &content[1..16]; + // The next part contains the file name + let file_name = match String::from_utf16(&content[17..]) { + Ok(name) => name, + Err(_) => { + println!("Failed to convert request to string"); + return Ok((KeysasAuthorization::AuthBlock, false)); + } + }; + + let file_path = Path::new(file_name.trim_matches(char::from(0))); + + println!("Received file ID: {:?} with name : {:?}", file_id, file_path); + + // Try to get the parent directory + let mut components = file_path.components(); + + // First component is the Root Directory + // If the second directory is "System Volume Information" then it is internal to windows, skip it + loop { + let c = components.next(); + if c.is_none() || c == Some(Component::RootDir) { + break; + } + } + + if components.next() == Some(Component::Normal(OsStr::new("System Volume Information"))) { + return Ok((KeysasAuthorization::AuthAllowAll, false)); + } + + // Skip the directories + if file_path.metadata()?.is_dir() { + return Ok((KeysasAuthorization::AuthAllowAll, false)); + } + + // Try to validate the file from the station report + match self.validate_file(file_path) { + Ok(true) => { + return Ok((KeysasAuthorization::AuthAllowRead, true)); + } + _ => { + println!("File not validated by station"); + } + } + + // If the validation fails, ask the user authorization + self.user_authorize_file(file_path).map(|r| (r, true)) + } + + /// Check a file + /// - If it is a normal file, try to find the corresponding station report + /// - If there is none, return False + /// - If there is one, validate both + /// - If the file is a station report, try to find the corresponding file + /// - If there is none, try to validate the report alone. There must be no file digest referenced in it + /// - If there is one, validate both + /// + /// # Arguments + /// + /// * 'path' - Path to the file + fn validate_file(&self, path: &Path) -> Result { + // Test if the file is a station report + if Path::new(path) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("krp")) + { + // Try to find the corresponding file + let mut file_path = path.to_path_buf(); + // file_path.file_name should not be None at this point + file_path.set_extension(""); + + match file_path.is_file() { + true => { + // If it exists, validate both + match parse_report(Path::new(path), Some(&file_path), + Some(&self.ca_cert_cl), + Some(&self.ca_cert_pq)) { + Ok(_) => return Ok(true), + Err(e) => { + println!("Failed to parse report: {e}"); + return Ok(false); + } + } + }, + false => { + // If no corresponding file validate it alone + match parse_report(Path::new(path), None, + Some(&self.ca_cert_cl), + Some(&self.ca_cert_pq)) { + Ok(_) => return Ok(true), + Err(e) => { + println!("Failed to parse report: {e}"); + return Ok(false); + } + } + } + } + } + + // If not try to find the corresponding report + // It should be in the same directory with the same name + '.krp' + let mut path_report = PathBuf::from(path); + match path_report.extension() { + Some(ext) => { + let mut ext = ext.to_os_string(); + ext.push(".krp"); + path_report.set_extension(ext); + }, + _ => { + path_report.set_extension(".krp"); + } + } + match path_report.is_file() { + true => { + // If a corresponding report is found then validate both the file and the report + if let Err(e) = parse_report(path_report.as_path(), Some(path), + Some(&self.ca_cert_cl), + Some(&self.ca_cert_pq)) { + println!("Failed to parse file and report: {e}"); + return Ok(false); + } + Ok(true) + } + false => { + // There is no corresponding report for validating the file + println!("No report found at {:?}", path_report); + Ok(false) + } + } + } + + /// Spawn a dialog box to ask the user to validate a file or not + /// Return Ok(true) or Ok(false) accordingly + /// + /// # Arguments + /// + /// * 'path' - Path to the file + fn user_authorize_file(&self, path: &Path) -> Result { + // Find authorization status for the file + let auth_request = format!("Allow file: {:?}", path.as_os_str()); + let (auth_request_ptr, _, _) = auth_request.into_raw_parts(); + + let authorization_status = unsafe { + MessageBoxA( + None, + PCSTR::from_raw(auth_request_ptr), + s!("Keysas USB Filter"), + MB_YESNO | MB_ICONWARNING | MB_SYSTEMMODAL, + ) + }; + + match authorization_status { + IDYES => { + Ok(KeysasAuthorization::AuthAllowRead) + } + IDNO => { + Ok(KeysasAuthorization::AuthBlock) + } + _ => { + Err(anyhow!(format!( + "Unknown Authorization: {:?}", + authorization_status + ))) + } + } + } +} \ No newline at end of file diff --git a/keysas-firewall/daemon/src/driver_interface.rs b/keysas-firewall/daemon/src/driver_interface.rs index 2dffc9a..6f9e946 100644 --- a/keysas-firewall/daemon/src/driver_interface.rs +++ b/keysas-firewall/daemon/src/driver_interface.rs @@ -7,7 +7,7 @@ //! KeysasDriverInterface is a generic interface to send and receive messages //! to the firewall driver in kernel space. -//! The interface directs call to the Windows interface +//! The interface must be specialized for Linux or Windows #![warn(unused_extern_crates)] #![forbid(non_shorthand_field_patterns)] @@ -24,24 +24,203 @@ #![warn(deprecated)] #![warn(unused_imports)] -use crate::windows_driver_interface::WindowsDriverInterface; - use anyhow::anyhow; +use std::mem::size_of; +use std::thread; +use libc::c_void; +use std::sync::Arc; +use serde::{Deserialize, Serialize}; +use widestring::U16CString; +use windows::core::PCWSTR; +use windows::Win32::Foundation::{ + CloseHandle, HANDLE, STATUS_SUCCESS, GetLastError +}; +use windows::Win32::Storage::InstallableFileSystems::{ + FilterConnectCommunicationPort, FilterGetMessage, FilterReplyMessage, FilterSendMessage, + FILTER_MESSAGE_HEADER, FILTER_REPLY_HEADER +}; -fn request_callback() { - log::info!("Called!"); -} +use crate::controller::ServiceController; -/// Initiliaze the driver interface depending on the OS -pub fn init_driver_com() -> Result { - if cfg!(windows) { - let driver_interface = WindowsDriverInterface::open_driver_com()?; +/// Operation code for the request from a driver to userland +#[derive(Debug, Clone, Copy)] +pub enum KeysasFilterOperation { + /// Validate the signature of the file and the report + ScanFile = 0, + /// Ask to validate the USB drive signature + ScanUsb +} - driver_interface.start_driver_com(request_callback)?; +/// Authorization states for files and USB devices +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum KeysasAuthorization { + /// Default value + AuthUnknown, + /// Authorization request pending + AuthPending, + /// Access is blocked + AuthBlock, + /// Access is allowed in read mode only + AuthAllowRead, + /// Access is allowed with a warning to the user + AuthAllowWarning, + /// Access is allowed for all operations + AuthAllowAll +} - Ok(driver_interface) - } else { - log::error!("OS not supported"); - Err(anyhow!("Failed to open driver interface: OS not supported")) +impl KeysasAuthorization { + pub fn as_u8(&self) -> u8 { + match self { + Self::AuthUnknown => 0, + Self::AuthPending => 1, + Self::AuthBlock => 2, + Self::AuthAllowRead => 3, + Self::AuthAllowWarning => 4, + Self::AuthAllowAll => 5 + } } } + +/// Format of a request from the driver to the service scanner +#[derive(Debug)] +#[repr(C)] +struct DriverRequest { + /// Header of the request managed by Windows + header: FILTER_MESSAGE_HEADER, + /// Operation code defined in [KeysasFilterOperation] + operation: KeysasFilterOperation, + /// Buffer with the content of the operation + content: [u16; 1024], +} + +/// Format of a reply to the driver +#[derive(Debug)] +#[repr(C)] +struct UserReply { + /// Header of the message, managed by Windows + header: FILTER_REPLY_HEADER, + /// Result of the request => the authorization state to apply to the file or USB device + result: KeysasAuthorization, +} + +/// Handle to the driver interface +#[derive(Debug, Copy, Clone)] +pub struct WindowsDriverInterface { + /// Handle to the communication port + handle: HANDLE, +} + +/// Name of the communication port with the driver +const DRIVER_COM_PORT: &str = "\\KeysasPort"; + +impl WindowsDriverInterface { + /// Initialize the interface to the Windows driver + /// The connection is made with the name in DRIVER_COM_PORT + pub fn open_driver_com() -> Result { + // Open communication canal with the driver + let com_port_name = U16CString::from_str(DRIVER_COM_PORT).unwrap().into_raw(); + + let handle = unsafe { + match FilterConnectCommunicationPort(PCWSTR(com_port_name), 0, None, 0, None) { + Ok(h) => h, + Err(e) => { + log::error!("Connection to minifilter failed: {e}"); + return Err(anyhow!("Connection to minifilter failed: {e}")); + } + } + }; + + Ok(Self { handle }) + } + + /// Start listening to the drivers' requests + /// + /// # Arguments + /// + /// * `cb` - Callback to handle the driver requests + pub fn start_driver_com(&self, ctrl: &Arc) -> Result<(), anyhow::Error> { + let handle = self.handle; + let ctrl_hdl = ctrl.clone(); + thread::spawn(move || -> Result<(), anyhow::Error> { + // Pre compute the request and response size + let request_size = u32::try_from(size_of::())?; + let reply_size = u32::try_from(size_of::())? + + u32::try_from(size_of::())?; + + loop { + // Wait for a request from the driver + let mut request = DriverRequest { + header: FILTER_MESSAGE_HEADER::default(), + operation: KeysasFilterOperation::ScanUsb, + content: [0; 1024], + }; + + unsafe { + if FilterGetMessage(handle, &mut request.header, request_size, None).is_err() + { + println!("Failed to get message from driver"); + continue; + } + } + + // Dispatch the request + let result = match ctrl_hdl.handle_driver_request(request.operation, &request.content) { + Ok(r) => r, + Err(e) => { + println!("Failed to handle driver request: {e}"); + KeysasAuthorization::AuthBlock + } + }; + + println!("Sending authorization: {:?}", result); + + // Prepare the response and send it + let reply = UserReply { + header: FILTER_REPLY_HEADER { + MessageId: request.header.MessageId, + Status: STATUS_SUCCESS, + }, + result, + }; + + unsafe { + if FilterReplyMessage(handle, &reply.header, reply_size).is_err() { + println!("Failed to send response to driver"); + continue; + } + } + } + }); + Ok(()) + } + + pub fn send_msg(&self, msg: &[u8]) -> Result<(), anyhow::Error> { + let mut nb_bytes_ret: u32 = 0; + unsafe { + if let Err(_) = FilterSendMessage( + self.handle, + msg as *const _ as *const c_void, + msg.len().try_into()?, + None, + 0, + &mut nb_bytes_ret as *mut u32 + ) { + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("Failed to send message to driver")); + } + } + + // TODO - Handle response from driver + + Ok(()) + } + + /// Close the communication with the driver + pub fn close_driver_com(&self) { + unsafe { + CloseHandle::(self.handle); + } + } +} \ No newline at end of file diff --git a/keysas-firewall/daemon/src/main.rs b/keysas-firewall/daemon/src/main.rs index 570c04b..bf4ae72 100644 --- a/keysas-firewall/daemon/src/main.rs +++ b/keysas-firewall/daemon/src/main.rs @@ -28,31 +28,99 @@ #![feature(str_split_remainder)] pub mod driver_interface; -pub mod windows_driver_interface; +pub mod tray_interface; +pub mod controller; -use crate::driver_interface::init_driver_com; +use crate::controller::ServiceController; +use clap::{crate_version, Arg, ArgAction, Command}; use anyhow::anyhow; +/// Configuration parameters for the service +#[derive(Debug)] +pub struct Config { + /// Path to the security policy configuration file + config: String, + /// Path to the CA ED25519 certificate + ca_cert_cl: String, + /// Path to the CA Dilithium 5 certificate + ca_cert_pq: String, + // TODO - Add revocation mecanism configuration (OCSP IP or CRL IP) +} + +impl Default for Config { + fn default() -> Self { + Self { + config: "./keysas-firewall-conf.toml".to_string(), + ca_cert_cl: "./st-ca-cl.pem".to_string(), + ca_cert_pq: "./st-ca-pq.pem".to_string() + } + } +} + +fn command_args(config: &mut Config) { + let matches = Command::new("keysas-usbfilter-daemon.exe") + .version(crate_version!()) + .author("Luc B.") + .about("Keysas firewall Windows service") + .arg( + Arg::new("config") + .short('c') + .long("config") + .value_name("Path to security policy configuration") + .default_value("./keysas-firewall-conf.toml") + .action(ArgAction::Set) + .help("Path to security policy configuration"), + ) + .arg( + Arg::new("ca_cl") + .short('l') + .long("ca_cl") + .value_name("Path to CA ED25519 certificate") + .default_value("./st-ca-cl.pem") + .action(ArgAction::Set) + .help("Path to CA ED25519 certificate"), + ) + .arg( + Arg::new("ca_pq") + .short('q') + .long("ca_pq") + .value_name("Path to CA Dilithium 5 certificate") + .default_value("./st-ca-pq.pem") + .action(ArgAction::Set) + .help("Path to CA Dilithium 5 certificate"), + ) + .get_matches(); + + //Won't panic according to clap authors because there are default values + if let Some(p) = matches.get_one::("config") { + config.config = p.to_string(); + } + if let Some(p) = matches.get_one::("ca_cl") { + config.ca_cert_cl = p.to_string(); + } + if let Some(p) = matches.get_one::("ca_pq") { + config.ca_cert_pq = p.to_string(); + } +} + fn main() -> Result<(), anyhow::Error> { // Initialize the logger simple_logger::init()?; - // Initialize the connection with the driver - if let Err(e) = init_driver_interface() { - log::error!("Failed to initialize communications with driver: {e}"); - return Err(anyhow!("Error: Driver interface initialization failed")); + // Get command arguments + let mut config = Config::default(); + command_args(&mut config); + + // Initialize and start the service + if let Err(e) = ServiceController::init(&config) { + log::error!("Failed to start the service: {e}"); + return Err(anyhow!("Failed to start the service: {e}")); } - log::info!("Driver interface OK"); + // Put the service in sleep until it receives request from the driver or the HMI loop { std::thread::sleep(std::time::Duration::from_secs(10)); } } - -// Initialize the driver interface and register the callbacks -fn init_driver_interface() -> Result<(), anyhow::Error> { - init_driver_com()?; - Ok(()) -} diff --git a/keysas-firewall/daemon/src/tray_interface.rs b/keysas-firewall/daemon/src/tray_interface.rs new file mode 100644 index 0000000..6fd3158 --- /dev/null +++ b/keysas-firewall/daemon/src/tray_interface.rs @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * + * (C) Copyright 2019-2023 Luc Bonnafoux, Stephane Neveu + * + */ + +//! Interface to the user tray application + +#![warn(unused_extern_crates)] +#![forbid(non_shorthand_field_patterns)] +#![warn(dead_code)] +#![warn(missing_debug_implementations)] +#![warn(missing_copy_implementations)] +#![warn(trivial_numeric_casts)] +#![warn(unused_extern_crates)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] +#![warn(variant_size_differences)] +#![forbid(private_in_public)] +#![warn(overflowing_literals)] +#![warn(deprecated)] +#![warn(unused_imports)] + +use anyhow::anyhow; +use serde::{Deserialize, Serialize}; +use libmailslot; +use std::sync::Arc; +use crate::controller::ServiceController; +use crate::driver_interface::KeysasAuthorization; + +/// Message for a file status notification +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct FileUpdateMessage { + device: String, + pub id: [u16; 16], + path: String, + pub authorization: KeysasAuthorization +} + +/// Name of the communication pipe +const SERVICE_PIPE: &str = r"\\.\mailslot\keysas\service-to-app"; +const TRAY_PIPE: &str = r"\\.\mailslot\keysas\app-to-service"; + +/// Initialize the server behind the interface +pub fn init(ctrl: &Arc) -> Result<(), anyhow::Error> { + let ctrl_hdl = ctrl.clone(); + // Initialize the server in a separate thread + std::thread::spawn(move || { + let server = match libmailslot::create_mailslot(TRAY_PIPE) { + Ok(s) => s, + Err(_) => return (), + }; + + loop { + while let Ok(Some(msg)) = libmailslot::read_mailslot(&server) { + if let Ok(update) = serde_json::from_slice::(msg.as_bytes()) { + println!("message from tray {:?}", update); + if let Err(e) = ctrl_hdl.handle_tray_request(&update) { + println!("Failed to handle tray request: {e}"); + } + } + } + std::thread::sleep(std::time::Duration::from_secs(1)); + } + + }); + Ok(()) +} + +/// Try to send a message to the connected socket +pub fn send(msg: &impl Serialize) -> Result<(), anyhow::Error> { + let msg_vec = match serde_json::to_string(msg) { + Ok(m) => m, + Err(e) => return Err(anyhow!("Failed to serialize message: {e}")) + }; + + if let Err(e) = libmailslot::write_mailslot(SERVICE_PIPE, &msg_vec) { + return Err(anyhow!("Failed to post message to the mailslot: {e}")); + } + + println!("Message sent"); + + Ok(()) +} + +pub fn send_file_auth_status(file_data: &[u16], authorization: KeysasAuthorization) -> Result<(), anyhow::Error> { + let file_path = match String::from_utf16(&file_data[17..]) { + Ok(path) => path, + Err(_) => { + println!("Failed to convert request to string"); + return Err(anyhow!("Failed to convert request to string")); + } + }; + let file_path = file_path.trim_matches(char::from(0)); + + let mut id: [u16; 16] = Default::default(); + id.copy_from_slice(&file_data[1..17]); + + let msg = FileUpdateMessage { + device: String::from("D:"), + id, + path: String::from(file_path), + authorization + }; + + send(&msg) +} \ No newline at end of file diff --git a/keysas-firewall/daemon/src/windows_driver_interface.rs b/keysas-firewall/daemon/src/windows_driver_interface.rs deleted file mode 100644 index 0c4d698..0000000 --- a/keysas-firewall/daemon/src/windows_driver_interface.rs +++ /dev/null @@ -1,491 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * - * (C) Copyright 2019-2023 Luc Bonnafoux, Stephane Neveu - * - */ - -//! KeysasDriverInterface is a generic interface to send and receive messages -//! to the firewall driver in kernel space. -//! The interface must be specialized for Linux or Windows - -#![warn(unused_extern_crates)] -#![forbid(non_shorthand_field_patterns)] -#![warn(dead_code)] -#![warn(missing_debug_implementations)] -#![warn(missing_copy_implementations)] -#![warn(trivial_numeric_casts)] -#![warn(unused_extern_crates)] -#![warn(unused_import_braces)] -#![warn(unused_qualifications)] -#![warn(variant_size_differences)] -#![forbid(private_in_public)] -#![warn(overflowing_literals)] -#![warn(deprecated)] -#![warn(unused_imports)] - -use anyhow::anyhow; -use libc::c_void; -use std::mem::size_of; -use std::path::PathBuf; -use std::path::{Component, Path}; -use std::thread; -use std::ffi::OsStr; -use widestring::U16CString; -use windows::core::{PCSTR, PCWSTR}; -use windows::s; -use windows::Win32::Foundation::{ - CloseHandle, GetLastError, BOOLEAN, HANDLE, STATUS_SUCCESS, STATUS_UNSUCCESSFUL, -}; -use windows::Win32::Storage::FileSystem::{ - CreateFileA, ReadFile, FILE_FLAG_BACKUP_SEMANTICS, FILE_SHARE_READ, FILE_SHARE_WRITE, - IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, OPEN_EXISTING, -}; -use windows::Win32::Storage::InstallableFileSystems::{ - FilterConnectCommunicationPort, FilterGetMessage, FilterReplyMessage, FILTER_MESSAGE_HEADER, - FILTER_REPLY_HEADER, -}; -use windows::Win32::System::Ioctl::VOLUME_DISK_EXTENTS; -use windows::Win32::System::IO::DeviceIoControl; -use windows::Win32::UI::WindowsAndMessaging::*; - -use keysas_lib::file_report::parse_report; - -/// Operation code for the request to userland -#[derive(Debug)] -enum KeysasFilterOperation { - /// Validate the signature of the file and the report - ScanFile = 0, - /// Ask user to allow the file - UserAllowFile, - /// Ask to validate the USB drive signature - ScanUsb, - /// Ask user to allow complete access the USB drive - UserAllowAllUsb, - /// Ask user to allow access to USB drive with warning on file opening - UserAllowUsbWithWarning, -} - -/// Format of a request from the driver to the service scanner -#[derive(Debug)] -#[repr(C)] -struct DriverRequest { - /// Header of the request managed by Windows - header: FILTER_MESSAGE_HEADER, - /// Operation code defined in [KeysasFilterOperation] - operation: KeysasFilterOperation, - /// Buffer with the content of the operation - content: [u16; 1024], -} - -/// Format of a reply to the driver -#[derive(Debug)] -#[repr(C)] -struct UserReply { - /// Header of the message, managed by Windows - header: FILTER_REPLY_HEADER, - /// Result of the request - result: BOOLEAN, -} - -/// Handle to the driver interface -#[derive(Debug, Copy, Clone)] -pub struct WindowsDriverInterface { - /// Handle to the communication port - handle: HANDLE, -} - -/// Name of the communication port with the driver -const DRIVER_COM_PORT: &str = "\\KeysasPort"; - -impl WindowsDriverInterface { - /// Initialize the interface to the Windows driver - /// The connection is made with the name in DRIVER_COM_PORT - pub fn open_driver_com() -> Result { - // Open communication canal with the driver - let com_port_name = U16CString::from_str(DRIVER_COM_PORT).unwrap().into_raw(); - - let handle = unsafe { - match FilterConnectCommunicationPort(PCWSTR(com_port_name), 0, None, 0, None) { - Ok(h) => h, - Err(e) => { - log::error!("Connection to minifilter failed: {e}"); - return Err(anyhow!("Connection to minifilter failed: {e}")); - } - } - }; - - Ok(Self { handle }) - } - - /// Start listening to the drivers' requests and register a callback to handle them - /// - /// # Arguments - /// - /// * `cb` - Callback to handle the driver requests - pub fn start_driver_com(&self, _cb: fn() -> ()) -> Result<(), anyhow::Error> { - let handle = self.handle; - thread::spawn(move || -> Result<(), anyhow::Error> { - let request_size = u32::try_from(size_of::())?; - let reply_size = u32::try_from(size_of::())? - + u32::try_from(size_of::())?; - - loop { - // Wait for a request from the driver - let mut request = DriverRequest { - header: FILTER_MESSAGE_HEADER::default(), - operation: KeysasFilterOperation::ScanFile, - content: [0; 1024], - }; - - unsafe { - if FilterGetMessage(handle, &mut request.header, request_size, None).is_err() - { - println!("Failed to get message from driver"); - continue; - } - } - - // Convert the request to Rust String - let content = match String::from_utf16(&request.content) { - Ok(c) => c, - Err(_) => { - println!("Failed to convert request to string"); - // Send error response to driver - let reply = UserReply { - header: FILTER_REPLY_HEADER { - MessageId: request.header.MessageId, - Status: STATUS_UNSUCCESSFUL, - }, - result: BOOLEAN::from(false), - }; - unsafe { - if FilterReplyMessage(handle, &reply.header, reply_size).is_err() { - println!("Failed to send response to driver"); - } - } - continue; - } - }; - - // Dispatch the request - let result = match request.operation { - KeysasFilterOperation::ScanFile | KeysasFilterOperation::UserAllowFile => { - matches!(authorize_file(request.operation, &content), Ok(true)) - } - KeysasFilterOperation::ScanUsb => true /*match authorize_usb(&content) { - Ok(true) => true, - _ => false, - }*/, - KeysasFilterOperation::UserAllowAllUsb => true, - KeysasFilterOperation::UserAllowUsbWithWarning => true, - }; - - // Prepare the response and send it - let reply = UserReply { - header: FILTER_REPLY_HEADER { - MessageId: request.header.MessageId, - Status: STATUS_SUCCESS, - }, - result: BOOLEAN::from(result), - }; - - unsafe { - if FilterReplyMessage(handle, &reply.header, reply_size).is_err() { - println!("Failed to send response to driver"); - continue; - } - } - } - }); - Ok(()) - } - - /// Close the communication with the driver - pub fn close_driver_com(&self) { - unsafe { - CloseHandle::(self.handle); - } - } -} - -/// Check a USB device to allow it not -/// Return Ok(true) or Ok(false) according to the authorization -/// -/// # Arguments -/// -/// * 'content' - Content of the request from the driver -fn authorize_usb(content: &str) -> Result { - println!("Received USB scan request: {:?}", content); - let mut buffer: [u8; 4096] = [0; 4096]; - let mut byte_read: u32 = 0; - - // Open the device on the first sector - let device = unsafe { - match CreateFileA( - s!("\\\\.\\D:"), - 1179785u32, - FILE_SHARE_READ | FILE_SHARE_WRITE, - None, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - None, - ) { - Ok(d) => d, - Err(_) => { - println!("Failed to open device"); - let err = GetLastError(); - println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); - return Err(anyhow!("Failed to open device")); - } - } - }; - - if device.is_invalid() { - println!("Invalid device handle"); - return Err(anyhow!("Invalid device handle")); - } - - let mut vde = VOLUME_DISK_EXTENTS::default(); - let mut dw: u32 = 0; - match unsafe { - DeviceIoControl( - device, - IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, - None, - 0, - Some(&mut vde as *mut _ as *mut c_void), - u32::try_from(size_of::())?, - Some(&mut dw), - None, - ).as_bool() - } { - true => (), - false => { - println!("Failed to query device"); - return Err(anyhow!("Failed to query device")); - - } - } - - let mut drive_path = String::from("\\\\.\\PhysicalDrive"); - drive_path.push_str(&vde.Extents[0].DiskNumber.to_string()); - - println!("Physical Drive path: {:?}", drive_path); - - let drive_str = PCSTR::from_raw(drive_path.as_ptr() as *const u8); - unsafe { - println!("Physical Drive path windows: {:?}", drive_str.to_string()?); - } - - let handle_usb = unsafe { - match CreateFileA( - drive_str, - 0, - FILE_SHARE_READ | FILE_SHARE_WRITE, - None, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - None, - ) { - Ok(d) => d, - Err(_) => { - println!("Failed to open usb"); - let err = GetLastError(); - println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); - return Err(anyhow!("Failed to open usb")); - } - } - }; - - if handle_usb.is_invalid() { - println!("Invalid device usb handle"); - return Err(anyhow!("Invalid device usb handle")); - } - - // Move the file pointer after the MBR table (512B) - // and read the signature content - let read = unsafe { - //SetFilePointer(device, 512, None, FILE_BEGIN); - ReadFile( - handle_usb, - Some(buffer.as_mut_ptr() as *mut c_void), - 4096, - Some(&mut byte_read), - None, - ) - }; - - if read.as_bool() { - println!("Device content: {:?}", buffer); - } else { - println!("Failed to read device content"); - unsafe { - let err = GetLastError(); - println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); - } - } - - Ok(true) -} - -/// Decide to authorize a file -/// Start by whitelisting file that belongs to Windows and remove directories -/// Then try to validate it with a station report -/// Finaly if it fails ask the user to validate it manualy -/// -/// USB_op will be used to apply a device wide filter policy -/// -/// # Arguments -/// -/// * 'usb_op' - Device wide filtering policy -/// * 'content' - Content of the driver request, it contains the path to the file -fn authorize_file(_usb_op: KeysasFilterOperation, content: &str) -> Result { - let file_path = Path::new(content.trim_matches(char::from(0))); - - // Try to get the parent directory - let mut components = file_path.components(); - - // First component is the Root Directory - // If the second directory is "System Volume Information" then it is internal to windows, skip it - loop { - let c = components.next(); - if c.is_none() || c == Some(Component::RootDir) { - break; - } - } - - if components.next() == Some(Component::Normal(OsStr::new("System Volume Information"))) { - return Ok(true); - } - - // Skip the directories - if file_path.metadata()?.is_dir() { - return Ok(true); - } - - // Try to validate the file from the station report - match validate_file(file_path) { - Ok(true) => { - return Ok(true); - } - _ => { - println!("File not validated by station"); - } - } - - // If the validation fails, ask the user authorization - user_authorize_file(file_path) -} - -/// Check a file -/// - If it is a normal file, try to find the corresponding station report -/// - If there is none, return False -/// - If there is one, validate both -/// - If the file is a station report, try to find the corresponding file -/// - If there is none, try to validate the report alone. There must be no file digest referenced in it -/// - If there is one, validate both -/// -/// # Arguments -/// -/// * 'path' - Path to the file -fn validate_file(path: &Path) -> Result { - // Test if the file is a station report - if Path::new(path) - .extension() - .is_some_and(|ext| ext.eq_ignore_ascii_case("krp")) - { - // Try to find the corresponding file - let mut file_path = path.to_path_buf(); - // file_path.file_name should not be None at this point - file_path.set_extension(""); - - match file_path.is_file() { - true => { - // If it exists, validate both - match parse_report(Path::new(path), Some(&file_path), None, None) { - Ok(_) => return Ok(true), - Err(e) => { - println!("Failed to parse report: {e}"); - return Ok(false); - } - } - }, - false => { - // If no corresponding file validate it alone - match parse_report(Path::new(path), None, None, None) { - Ok(_) => return Ok(true), - Err(e) => { - println!("Failed to parse report: {e}"); - return Ok(false); - } - } - } - } - } - - // If not try to find the corresponding report - // It should be in the same directory with the same name + '.krp' - let mut path_report = PathBuf::from(path); - match path_report.extension() { - Some(ext) => { - let mut ext = ext.to_os_string(); - ext.push(".krp"); - path_report.set_extension(ext); - }, - _ => { - path_report.set_extension(".krp"); - } - } - match path_report.is_file() { - true => { - // If a corresponding report is found then validate both the file and the report - if let Err(e) = parse_report(path_report.as_path(), Some(path), None, None) { - println!("Failed to parse file and report: {e}"); - return Ok(false); - } - Ok(true) - } - false => { - // There is no corresponding report for validating the file - println!("No report found at {:?}", path_report); - Ok(false) - } - } -} - -/// Spawn a dialog box to ask the user to validate a file or not -/// Return Ok(true) or Ok(false) accordingly -/// -/// # Arguments -/// -/// * 'path' - Path to the file -fn user_authorize_file(path: &Path) -> Result { - // Find authorization status for the file - let auth_request = format!("Allow file: {:?}", path.as_os_str()); - let (auth_request_ptr, _, _) = auth_request.into_raw_parts(); - - let authorization_status = unsafe { - MessageBoxA( - None, - PCSTR::from_raw(auth_request_ptr), - s!("Keysas USB Filter"), - MB_YESNO | MB_ICONWARNING | MB_SYSTEMMODAL, - ) - }; - - match authorization_status { - IDYES => { - Ok(true) - } - IDNO => { - Ok(false) - } - _ => { - Err(anyhow!(format!( - "Unknown Authorization: {:?}", - authorization_status - ))) - } - } -} diff --git a/keysas-firewall/libmailslot/Cargo.toml b/keysas-firewall/libmailslot/Cargo.toml new file mode 100644 index 0000000..1a361af --- /dev/null +++ b/keysas-firewall/libmailslot/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "libmailslot" +version = "0.1.0" +edition = "2021" +description = "Windows mailslot wrapper library" +authors = ["Luc Bonnafoux", "Stephane N"] +license = "GPL-3.0" +repository = "" + +[dependencies] +anyhow = "1.0" +libc = "0.2" + +[dependencies.windows] +version = "0.48.0" +features = [ + "Win32_Foundation", + "Win32_Security", + "Win32_System_Mailslots", + "Win32_System_SystemServices", + "Win32_Storage_FileSystem", + "Win32_System_IO" +] \ No newline at end of file diff --git a/keysas-firewall/libmailslot/src/lib.rs b/keysas-firewall/libmailslot/src/lib.rs new file mode 100644 index 0000000..7baabee --- /dev/null +++ b/keysas-firewall/libmailslot/src/lib.rs @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * + * (C) Copyright 2019-2023 Luc Bonnafoux, Stephane Neveu + * + */ + +//! Simple wrapper around the Windows mailslot API + +#![warn(unused_extern_crates)] +#![forbid(non_shorthand_field_patterns)] +#![warn(dead_code)] +#![warn(missing_debug_implementations)] +#![warn(missing_copy_implementations)] +#![warn(trivial_numeric_casts)] +#![warn(unused_extern_crates)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] +#![warn(variant_size_differences)] +#![forbid(private_in_public)] +#![warn(overflowing_literals)] +#![warn(deprecated)] +#![warn(unused_imports)] + +use anyhow::anyhow; +use windows::Win32::Foundation::{HANDLE, GetLastError, BOOL, FALSE}; +use windows::Win32::System::Mailslots::{GetMailslotInfo, CreateMailslotW}; +use windows::Win32::System::SystemServices::MAILSLOT_WAIT_FOREVER; +use windows::Win32::Storage::FileSystem::{CreateFileW, WriteFile, ReadFile, FILE_SHARE_READ, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL}; +use windows::core::PCWSTR; +use windows::Win32::Security::{InitializeSecurityDescriptor, SECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR, + SECURITY_ATTRIBUTES, SE_DACL_PROTECTED, SetSecurityDescriptorControl, SetSecurityDescriptorDacl}; +use windows::Win32::System::SystemServices::SECURITY_DESCRIPTOR_REVISION; +use libc::c_void; +use std::str; +use std::{ffi::OsStr, iter::once, os::windows::ffi::OsStrExt}; + +const MAX_MSG_SIZE: u32 = 1024; + +/// Handle to the mailslot +#[derive(Debug, Copy, Clone)] +pub struct MailSlot { + pub handle: HANDLE +} + +/// Create a new mailslot +/// +/// # Arguments +/// +/// * `name` - Name of the mailslot +pub fn create_mailslot(name: &str) -> Result { + // let slot_name = PCSTR::from_raw(name.as_ptr() as *const u8); + let slot_name: Vec = OsStr::new(name).encode_wide().chain(once(0)).collect(); + let pslot_name = PCWSTR::from_raw(slot_name.as_ptr() as *const u16); + + // Give complete access to the mailslot + let mut sec_dec = SECURITY_DESCRIPTOR::default(); + let mut psec_desc = PSECURITY_DESCRIPTOR::default(); + psec_desc.0 = &mut sec_dec as *mut SECURITY_DESCRIPTOR as *mut c_void; + + unsafe { + if !InitializeSecurityDescriptor( + psec_desc, + SECURITY_DESCRIPTOR_REVISION).as_bool() { + println!("run_server: Failed to initialize the security descriptor"); + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("run_server: Failed to initialize the security descriptor")); + } + + if !SetSecurityDescriptorDacl( + psec_desc, + BOOL::from(true), + None, + BOOL::from(false)).as_bool() { + println!("run_server: Failed to set the security descriptor Dacl"); + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("run_server: Failed to set the security descriptor Dacl")); + } + + if !SetSecurityDescriptorControl( + psec_desc, + SE_DACL_PROTECTED, + SE_DACL_PROTECTED).as_bool() { + println!("run_server: Failed to set the security descriptor Control"); + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("run_server: Failed to set the security descriptor Control")); + } + } + + let mut sec_attr = SECURITY_ATTRIBUTES::default(); + sec_attr.lpSecurityDescriptor = psec_desc.0; + sec_attr.bInheritHandle = FALSE; + + let handle = unsafe { + match CreateMailslotW( + pslot_name, + MAX_MSG_SIZE, + MAILSLOT_WAIT_FOREVER, + Some(&sec_attr as *const SECURITY_ATTRIBUTES) + ) { + Ok(h) => h, + Err(_) => { + println!("create_mailslot: Failed to create mailslot: {:?}", name); + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("create_mailslot: Failed to create mailslot")); + } + } + }; + + if handle.is_invalid() { + println!("create_mailslot: Invalid mailslot handle"); + unsafe { + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + } + return Err(anyhow!("create_mailslot: Invalid mailslot handle")); + + } + + Ok(MailSlot{handle}) +} + +/// Read one message from the mailslot +/// +/// # Arguments +/// +/// * `mailslot` - Handle to the mailslot +/// * `handle_msg` - Callback to handle messages received +pub fn read_mailslot(mailslot: &MailSlot) -> Result, anyhow::Error> { + // Retrieve state of the mailslot + let mut next_msg_size: u32 = 0; + let mut nb_msg: u32 = 0; + unsafe { + if !GetMailslotInfo( + mailslot.handle, + None, + Some(&mut next_msg_size), + Some(&mut nb_msg), + None + ).as_bool() { + println!("read_mailslot: Failed to read mailslot info"); + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("read_mailslot: Failed to read mailslot info")); + } + } + + // If there is no message to read exit + if nb_msg == 0 { + return Ok(None); + } + + // Read the message from the mailslot + let mut buffer: [u8; MAX_MSG_SIZE as usize] = [0; MAX_MSG_SIZE as usize]; + unsafe { + if !ReadFile( + mailslot.handle, + Some(buffer.as_mut_ptr() as *mut c_void), + next_msg_size, + None, + None + ).as_bool() { + println!("read_mailslot: Failed to read message"); + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("read_mailslot: Failed to read message")); + } + } + + match str::from_utf8(&buffer) { + Ok(msg) => { + let res = msg.trim_matches(char::from(0)); + return Ok(Some(String::from(res)));} + Err(e) => {return Err(anyhow!("read_mailslot: Failed to read message {e}"));} + } +} + +/// Write a message to a mailbox +/// +/// # Arguments +/// +/// * `name` - Name of the mailslot +/// * `message` - Message to write +pub fn write_mailslot(name: &str, message: &str) -> Result<(), anyhow::Error> { + // Check that message is no longer than the maximum message size + // Conversion from u32 to usize should not panic as usize should be at least 32 bit wide on targets + if message.len() > usize::try_from(MAX_MSG_SIZE).unwrap() { + println!("write_mailslot: message too long"); + return Err(anyhow!("write_mailslot: message too long")); + } + + // The mailslot is accessed like a file + // Create a handle to the file + //let slot_name = PCSTR::from_raw(name.as_ptr() as *const u8); + let slot_name: Vec = OsStr::new(name).encode_wide().chain(once(0)).collect(); + let pslot_name = PCWSTR::from_raw(slot_name.as_ptr() as *const u16); + + let tmp_handle = HANDLE::default(); + // GENERIC_WRITE corresponds to the 30th bit of the mask + // according to https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask-format + let generic_write_val: u32 = 0x40000000; + let handle = unsafe { + match CreateFileW( + pslot_name, + generic_write_val, + FILE_SHARE_READ, + None, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + tmp_handle + ) { + Ok(h) => h, + Err(_) => { + println!("write_mailslot: Failed to create file"); + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("write_mailslot: Failed to create file")); + } + } + }; + + if handle.is_invalid() { + println!("write_mailslot: Invalid mailslot handle"); + unsafe { + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + } + return Err(anyhow!("write_mailslot: Invalid mailslot handle")); + + } + + // Write to the file + unsafe { + if !WriteFile( + handle, + Some(message.as_bytes()), + None, + None + ).as_bool() { + println!("write_mailslot: Failed to write to file"); + let err = GetLastError(); + println!("Error: {:?}", err.to_hresult().message().to_string_lossy()); + return Err(anyhow!("write_mailslot: Failed to write to file")); + } + } + + Ok(()) +} \ No newline at end of file diff --git a/keysas-firewall/minifilter/KeysasDriver.vcxproj b/keysas-firewall/minifilter/KeysasDriver.vcxproj index aa806d1..8c90aba 100644 --- a/keysas-firewall/minifilter/KeysasDriver.vcxproj +++ b/keysas-firewall/minifilter/KeysasDriver.vcxproj @@ -85,7 +85,7 @@ sha256 - $(DDK_LIB_PATH)fltMgr.lib;%(AdditionalDependencies) + $(DDK_LIB_PATH)fltMgr.lib;$(DDK_LIB_PATH)ksecdd.lib;%(AdditionalDependencies) @@ -93,7 +93,7 @@ sha256 - $(DDK_LIB_PATH)fltMgr.lib;%(AdditionalDependencies) + $(DDK_LIB_PATH)fltMgr.lib;$(DDK_LIB_PATH)ksecdd.lib;%(AdditionalDependencies) diff --git a/keysas-firewall/minifilter/keysasCommunication.c b/keysas-firewall/minifilter/keysasCommunication.c index f65f763..ef7b86e 100644 --- a/keysas-firewall/minifilter/keysasCommunication.c +++ b/keysas-firewall/minifilter/keysasCommunication.c @@ -24,8 +24,10 @@ Module Name: #include #include #include +#include #include "keysasDriver.h" +#include "keysasFile.h" // Name of the port used to communicate with user space const PWSTR KeysasPortName = L"\\KeysasPort"; @@ -33,6 +35,7 @@ const PWSTR KeysasPortName = L"\\KeysasPort"; #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, KeysasPortConnect) #pragma alloc_text(PAGE, KeysasPortDisconnect) +#pragma alloc_text(PAGE, KeysasPortNotify) #endif NTSTATUS @@ -72,7 +75,7 @@ Return Value NULL, KeysasPortConnect, KeysasPortDisconnect, - NULL, + KeysasPortNotify, 1 ); @@ -154,4 +157,78 @@ Return value FltCloseClientPort(KeysasData.Filter, &KeysasData.ClientPort); KeysasData.UserProcess = NULL; +} + +NTSTATUS +KeysasPortNotify ( + _In_ PVOID ConnectionCookie, + _In_reads_bytes_opt_(InputBufferSize) PVOID InputBuffer, + _In_ ULONG InputBufferSize, + _Out_writes_bytes_to_opt_(OutputBufferSize, *ReturnOutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferSize, + _Out_ PULONG ReturnOutputBufferLength +) +/*++ +Routine Description + This is called when a request is received from userspace +Arguments + PortCookie - Cookie that identifies the user, not use as there is only one connection from the service + InputBuffer - Buffer containing the request, it is allocated by the userspace + InputBufferLength - Length of the input buffer + OutputBuffer - Buffer for the response, it is allocated by the userspace + OutputBufferLength - Length of the output buffer + ReturnOutputBufferlength - Length of the response +Return value + None +--*/ +{ + UNREFERENCED_PARAMETER(ConnectionCookie); + UNREFERENCED_PARAMETER(OutputBuffer); + UNREFERENCED_PARAMETER(OutputBufferSize); + + PLIST_ENTRY scan, next; + PKEYSAS_FILE_CTX fileCtx = NULL; + KIRQL kIrql; + PUCHAR inputBuffer = (PUCHAR)InputBuffer; + + ReturnOutputBufferLength = 0; + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasPortNotify: Entered\n")); + + // Test that the input buffer contains at least 33 bytes + if (33 > InputBufferSize || NULL == InputBuffer) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasPortNotify: Not enough input data\n")); + return STATUS_UNSUCCESSFUL; + } + + // Test if the file context list is empty + if (TRUE == IsListEmpty(&KeysasData.FileCtxListHead)) { + // TODO - Send response to userspace + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasPortNotify: File context list is empty\n")); + return STATUS_SUCCESS; + } + + kIrql = KeGetCurrentIrql(); + + // Get lock on the list + KeAcquireSpinLock(&KeysasData.FileCtxListLock, &kIrql); + + // Go through the list to find the context + for (scan = (KeysasData.FileCtxListHead).Flink, next = scan->Flink; scan != &(KeysasData.FileCtxListHead); scan = next, next = scan->Flink) { + fileCtx = CONTAINING_RECORD(scan, KEYSAS_FILE_CTX, FileCtxList); + + if (32 == RtlCompareMemory(fileCtx->FileID, inputBuffer, 32)) { + // Changed the authorization status to the one provided by the service + fileCtx->Authorization = inputBuffer[32]; + break; + } + } + + KeReleaseSpinLock(&KeysasData.FileCtxListLock, kIrql); + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasPortNotify: Done\n")); + + // If the context is found apply the request + + return STATUS_SUCCESS; } \ No newline at end of file diff --git a/keysas-firewall/minifilter/keysasCommunication.h b/keysas-firewall/minifilter/keysasCommunication.h index 352e648..d3785ff 100644 --- a/keysas-firewall/minifilter/keysasCommunication.h +++ b/keysas-firewall/minifilter/keysasCommunication.h @@ -26,6 +26,8 @@ Module Name: #include #include +#include "keysasDriver.h" + #define KEYSAS_REQUEST_BUFFER_SIZE 1024 // Operation code for the request to userland @@ -48,8 +50,8 @@ typedef struct _KEYSAS_DRIVER_REQUEST { // Structure of a reply from user space typedef struct _KEYSAS_REPLY { // Result of the operation - // allow or not usb or file - BOOLEAN Result; + // Authorization status granted to usb or file + KEYSAS_AUTHORIZATION Result; } KEYSAS_REPLY, * PKEYSAS_REPLY; NTSTATUS @@ -69,4 +71,14 @@ KeysasPortDisconnect( _In_opt_ PVOID ConnectionCookie ); +NTSTATUS +KeysasPortNotify ( + _In_ PVOID ConnectionCookie, + _In_reads_bytes_opt_(InputBufferSize) PVOID InputBuffer, + _In_ ULONG InputBufferSize, + _Out_writes_bytes_to_opt_(OutputBufferSize, *ReturnOutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferSize, + _Out_ PULONG ReturnOutputBufferLength +); + #endif // _H_KEYSAS_COMMUNICATION diff --git a/keysas-firewall/minifilter/keysasDriver.c b/keysas-firewall/minifilter/keysasDriver.c index 203f117..e80044e 100644 --- a/keysas-firewall/minifilter/keysasDriver.c +++ b/keysas-firewall/minifilter/keysasDriver.c @@ -29,6 +29,7 @@ Module Name: #include #include #include +#include #include "keysasDriver.h" #include "keysasUtils.h" @@ -76,6 +77,11 @@ KfUnload( // operation registration // CONST FLT_OPERATION_REGISTRATION Callbacks[] = { + // IRP_MJ_WRITE + {IRP_MJ_WRITE, + 0, + KfPreWriteHandler, + NULL}, { IRP_MJ_CREATE, 0, @@ -161,6 +167,7 @@ Return Value: --*/ { NTSTATUS status = STATUS_SUCCESS; + DWORD cbData = 0; UNREFERENCED_PARAMETER(RegistryPath); @@ -168,6 +175,47 @@ Return Value: ExInitializeDriverRuntime(DrvRtPoolNxOptIn); + // Initialize global data structure + RtlZeroMemory(&KeysasData, sizeof(KeysasData)); + InitializeListHead(&KeysasData.FileCtxListHead); + KeInitializeSpinLock(&KeysasData.FileCtxListLock); + + if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider( + &KeysasData.HashProvider, + BCRYPT_SHA256_ALGORITHM, + NULL, + 0 + ))) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas: Failed to get hash provider\n")); + return status; + } + + // Get the internal hash object size + if (!NT_SUCCESS(status = BCryptGetProperty( + KeysasData.HashProvider, + BCRYPT_OBJECT_LENGTH, + (PUCHAR) &KeysasData.HashObjectSize, + sizeof(DWORD), + &cbData, + 0 + ))) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas: Failed to get hash object size\n")); + return status; + } + + // Get the hash size + if (!NT_SUCCESS(status = BCryptGetProperty( + KeysasData.HashProvider, + BCRYPT_HASH_LENGTH, + (PUCHAR)&KeysasData.HashLength, + sizeof(DWORD), + &cbData, + 0 + ))) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas: Failed to get hash length\n")); + return status; + } + // Register the filter's callbacks status = FltRegisterFilter(DriverObject, &FilterRegistration, @@ -229,6 +277,11 @@ Return Value: KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfUnload: Closed server port\n")); + // Release crypto provider + BCryptCloseAlgorithmProvider(KeysasData.HashProvider, 0); + + // TODO - Release all context in the list + FltUnregisterFilter(KeysasData.Filter); KeysasData.Filter = NULL; KeysasData.ServerPort = NULL; diff --git a/keysas-firewall/minifilter/keysasDriver.h b/keysas-firewall/minifilter/keysasDriver.h index b81f76e..21854cb 100644 --- a/keysas-firewall/minifilter/keysasDriver.h +++ b/keysas-firewall/minifilter/keysasDriver.h @@ -25,6 +25,7 @@ Module Name: #include #include #include +#include // Memory Pool Tag #define KEYSAS_MEMORY_TAG 'eKlF' @@ -45,6 +46,22 @@ typedef struct _KEYSAS_DATA { // Connection port to user-mode PFLT_PORT ClientPort; + + // List head of the file context tracked by the driver + LIST_ENTRY FileCtxListHead; + + // Lock to synchronize accesses to the file context list + KSPIN_LOCK FileCtxListLock; + + // Handle to the crypto provider + BCRYPT_ALG_HANDLE HashProvider; + + // Hash parameters + // Hash internal object size + unsigned long HashObjectSize; + + // Length of the hash + unsigned long HashLength; } KEYSAS_DATA, * PKEYSAS_DATA; extern KEYSAS_DATA KeysasData; diff --git a/keysas-firewall/minifilter/keysasFile.c b/keysas-firewall/minifilter/keysasFile.c index d10a555..118b232 100644 --- a/keysas-firewall/minifilter/keysasFile.c +++ b/keysas-firewall/minifilter/keysasFile.c @@ -32,7 +32,7 @@ Module Name: #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, KfFileContextCleanup) -#pragma alloc_text(PAGE, FindFileContext) +//#pragma alloc_text(PAGE, FindFileContext) #pragma alloc_text(PAGE, KfPostCreateHandler) #pragma alloc_text(PAGE, KfPreCreateHandler) #pragma alloc_text(PAGE, KeysasScanFileInUserMode) @@ -60,7 +60,10 @@ Return Value: switch (ContextType) { case FLT_FILE_CONTEXT: fileContext = (PKEYSAS_FILE_CTX)Context; - if (fileContext->Resource != NULL) { + if (NULL != fileContext->FileID) { + ExFreePoolWithTag(fileContext->FileID, KEYSAS_MEMORY_TAG); + } + if (NULL != fileContext->Resource) { ExDeleteResourceLite(fileContext->Resource); ExFreePoolWithTag(fileContext->Resource, KEYSAS_MEMORY_TAG); } @@ -103,7 +106,7 @@ Return Value: PKEYSAS_FILE_CTX fileContext = NULL; PKEYSAS_FILE_CTX oldFileContext = NULL; - PAGED_CODE(); + //PAGED_CODE(); // Initialize output paramters *FileContext = NULL; @@ -144,6 +147,7 @@ Return Value: // Initialize the context // Set all the fields to 0 => Authorization = UNKNOWN RtlZeroMemory(fileContext, KEYSAS_FILE_CTX_SIZE); + // Initialize the lock fileContext->Resource = ExAllocatePoolZero( NonPagedPool, sizeof(ERESOURCE), @@ -156,6 +160,13 @@ Return Value: return STATUS_INSUFFICIENT_RESOURCES; } ExInitializeResourceLite(fileContext->Resource); + fileContext->FileID = NULL; + + // Initialize and place the context in the list + ExInterlockedInsertHeadList( + &KeysasData.FileCtxListHead, + &fileContext->FileCtxList, + &KeysasData.FileCtxListLock); // Attach the context to the file status = FltSetFileContext( @@ -239,8 +250,7 @@ Return Value: // Don't filter call to directories or volumes status = FltIsDirectory(Data->Iopb->TargetFileObject, FltObjects->Instance, &isDirectory); - if (((Data->Iopb->TargetFileObject->Flags & FO_VOLUME_OPEN) == TRUE) || - ((Data->Iopb->TargetFileObject->FileName.Length == 0) && (Data->Iopb->TargetFileObject->RelatedFileObject == NULL)) || + if (((Data->Iopb->TargetFileObject->FileName.Length == 0) && (Data->Iopb->TargetFileObject->RelatedFileObject == NULL)) || isDirectory) { return FLT_PREOP_SUCCESS_NO_CALLBACK; @@ -252,6 +262,12 @@ Return Value: if (FlagOn(Data->Iopb->OperationFlags, SL_OPEN_TARGET_DIRECTORY)) { return FLT_PREOP_SUCCESS_NO_CALLBACK; } + if (FlagOn(Data->Iopb->OperationFlags, SL_OPEN_PAGING_FILE)) { + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + if (FlagOn(FltObjects->FileObject->Flags, FO_VOLUME_OPEN)) { + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } // Get the instance context // If the instance is blocked, reject all calls @@ -269,29 +285,34 @@ Return Value: // Get a read lock on the instance context if (!AcquireResourceRead(instanceContext->Resource)) { KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPostCreateHandler: Failed to acquire ressource in read mode\n")); + FltReleaseContext(instanceContext); + return FLT_PREOP_SUCCESS_NO_CALLBACK; } + // Apply authorization state to the call switch (instanceContext->Authorization) { case AUTH_BLOCK: // Block all calls Data->IoStatus.Status = STATUS_ACCESS_DENIED; Data->IoStatus.Information = 0; - ReleaseResource(instanceContext->Resource); - return FLT_PREOP_COMPLETE; + FltSetCallbackDataDirty(Data); + result = FLT_PREOP_COMPLETE; break; case AUTH_ALLOW_ALL: // Allow all call without verification - ReleaseResource(instanceContext->Resource); - return FLT_PREOP_SUCCESS_NO_CALLBACK; + result = FLT_PREOP_SUCCESS_NO_CALLBACK; break; case AUTH_UNKNOWN: case AUTH_PENDING: // These two states should not happen KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreCreateHandler: Invalid instance authorization state\n")); default: + result = FLT_PREOP_SUCCESS_WITH_CALLBACK; break; } + + // Release resources ReleaseResource(instanceContext->Resource); FltReleaseContext(instanceContext); @@ -336,7 +357,6 @@ Return Value: { NTSTATUS status = STATUS_SUCCESS; PFLT_FILE_NAME_INFORMATION nameInfo = NULL; - BOOLEAN safeToOpen = TRUE; PKEYSAS_FILE_CTX fileContext = NULL; BOOLEAN contextCreated = FALSE; KEYSAS_FILTER_OPERATION operation = SCAN_FILE; @@ -404,8 +424,18 @@ Return Value: } // Test the authorization again as it can have been preempted if (AUTH_UNKNOWN == fileContext->Authorization) { + // Resume file context initialization fileContext->Authorization = AUTH_PENDING; + IoQueryFileDosDeviceName(FltObjects->FileObject, &msFileName); + + // Compute hash of file name to store it in the file context + if (STATUS_SUCCESS != KeysasGetFileNameHash(&msFileName->Name, &(fileContext->FileID))) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPostCreateHandler: Failed to compute file name hash\n")); + status = FLT_POSTOP_FINISHED_PROCESSING; + goto cleanup; + } + // Try to acquire the instance context as the file authorization will depend on the instance status status = FindInstanceContext( Data->Iopb->TargetInstance, @@ -416,6 +446,7 @@ Return Value: KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPostCreateHandler: FindInstanceContext failed with status: %0x8x\n", status)); status = FLT_POSTOP_FINISHED_PROCESSING; + ReleaseResource(fileContext->Resource); goto cleanup; } @@ -441,22 +472,14 @@ Return Value: ReleaseResource(instanceContext->Resource); // Send the file to further analysis in user space KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPostCreateHandler: Send request to userspace\n")); - IoQueryFileDosDeviceName(FltObjects->FileObject, &msFileName); (VOID)KeysasScanFileInUserMode( &msFileName->Name, + fileContext->FileID, operation, - &safeToOpen + &fileContext->Authorization ); - KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPostCreateHandler: Received authorization from userspace\n")); - // Allow or block the file depending on the userspace result - if (TRUE == safeToOpen) { - fileContext->Authorization = AUTH_ALLOW_READ; - KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPostCreateHandler: File authorization ALLOW\n")); - } - else { - fileContext->Authorization = AUTH_BLOCK; - KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPostCreateHandler: File authorization BLOCK\n")); - } + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPostCreateHandler: Received authorization status from userspace: %0x\n", + fileContext->Authorization)); break; case AUTH_ALLOW_ALL: // Set the file to allow mode @@ -478,6 +501,7 @@ Return Value: // Block the transaction Data->IoStatus.Status = STATUS_ACCESS_DENIED; Data->IoStatus.Information = 0; + FltSetCallbackDataDirty(Data); KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPostCreateHandler: File blocked\n")); break; case AUTH_PENDING: @@ -510,14 +534,257 @@ Return Value: FltReleaseContext(instanceContext); } + if (NULL != msFileName) { + ExFreePool(msFileName); + } + + return status; +} + +FLT_PREOP_CALLBACK_STATUS +KfPreWriteHandler( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID* CompletionContext +) +/*** +Routine Description: + This routine is a registered callback called before any operation that can modify a file. + It retrieves the file context and check that it is authorize in "Write" mode, else it blocks the operation. + This is non-pageable because it could be called on the paging path. +Arguments: + Data - Pointer to the filter callback data + FltObjects - Pointer to the structure containing handles to the filter, instance, associated volume and file. + CompletionContext - Optional context that can be passed to the post callback. NULL in this case. +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PKEYSAS_FILE_CTX fileContext = NULL; + PKEYSAS_INSTANCE_CTX instanceContext = NULL; + BOOLEAN contextCreated = FALSE; + BOOLEAN isDirectory = FALSE; + PFLT_FILE_NAME_INFORMATION nameInfo = NULL; + FLT_PREOP_CALLBACK_STATUS result = FLT_PREOP_SUCCESS_NO_CALLBACK; + + UNREFERENCED_PARAMETER(CompletionContext); + + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: Entered\n")); + status = FltGetFileNameInformation( + Data, + FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, + &nameInfo + ); + if (!NT_SUCCESS(status)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: FltGetFileNameInformation failed with status: %0x8x\n", + status)); + status = FLT_POSTOP_FINISHED_PROCESSING; + goto cleanup; + } + + FltParseFileNameInformation(nameInfo); + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: File Name:%wZ, Extension: %wZ, Volume: %wZ\n", + nameInfo->Name, + nameInfo->Extension, + nameInfo->Volume)); + + // Filter call + // Allow call from our userspace application + if (IoThreadToProcess(Data->Thread) == KeysasData.UserProcess) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: User process call exit\n")); + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + // Don't filter call to directories or volumes + status = FltIsDirectory(Data->Iopb->TargetFileObject, FltObjects->Instance, &isDirectory); + + if (((Data->Iopb->TargetFileObject->Flags & FO_VOLUME_OPEN) == TRUE) || + ((Data->Iopb->TargetFileObject->FileName.Length == 0) && (Data->Iopb->TargetFileObject->RelatedFileObject == NULL)) || + isDirectory) + { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: directory 1 exit\n")); + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + + if (FlagOn(Data->Iopb->Parameters.Create.Options, FILE_DIRECTORY_FILE)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: directory 2 exit\n")); + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + if (FlagOn(Data->Iopb->OperationFlags, SL_OPEN_TARGET_DIRECTORY)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: directory 3 exit\n")); + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + + // Check that the instance is allowed + // Get the instance context + status = FltGetInstanceContext( + Data->Iopb->TargetInstance, + &instanceContext + ); + if (!NT_SUCCESS(status)) { + // There should always be a context for an instance to which the filter is attached + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: FltGetInstanceContext failed with status: %0x8x\n", + status)); + goto cleanup; + } + + // Get a read lock on the instance context + if (!AcquireResourceRead(instanceContext->Resource)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: Failed to acquire instance ressource in read mode\n")); + FltReleaseContext(instanceContext); + goto cleanup; + } + + // Apply authorization state to the call + if ((AUTH_ALLOW_ALL != instanceContext->Authorization) + && (AUTH_ALLOW_WARNING != instanceContext->Authorization)) { + // The instance is blocked, reject the operation + Data->IoStatus.Status = STATUS_ACCESS_DENIED; + Data->IoStatus.Information = 0; + FltSetCallbackDataDirty(Data); + result = FLT_PREOP_COMPLETE; + ReleaseResource(instanceContext->Resource); + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: Instance blocked, operation rejected\n")); + goto cleanup; + } + ReleaseResource(instanceContext->Resource); + + // Get the file context + status = FindFileContext(Data, &fileContext, &contextCreated); + if (!NT_SUCCESS(status)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: FindFileContext failed with status: %0x8x\n", + status)); + goto cleanup; + } + + // Get a read lock on the file context + if (!AcquireResourceRead(fileContext->Resource)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: Failed to acquire file ressource in read mode\n")); + FltReleaseContext(fileContext); + goto cleanup; + } + + // Apply authorization state to the call + if (AUTH_ALLOW_ALL != fileContext->Authorization) { + // The file is blocked, reject the operation + Data->IoStatus.Status = STATUS_ACCESS_DENIED; + Data->IoStatus.Information = 0; + FltSetCallbackDataDirty(Data); + result = FLT_PREOP_COMPLETE; + ReleaseResource(fileContext->Resource); + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: File blocked, operation rejected\n")); + goto cleanup; + } + ReleaseResource(fileContext->Resource); + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfPreWriteHandler: Operation allowed\n")); + result = FLT_PREOP_SUCCESS_NO_CALLBACK; + +cleanup: + if (NULL != instanceContext) { + FltReleaseContext(instanceContext); + } + if (NULL != fileContext) { + FltReleaseContext(fileContext); + } + + return result; +} + +NTSTATUS +KeysasGetFileNameHash( + _In_ PUNICODE_STRING FileName, + _Out_ PUCHAR *Hash +) +/*** +Routine Description: + This routine compute the hash of a file name and stores it in a buffer. + This routine uses the crypto provider initialize in the global KeysasData. +Arguments: + FileName - Name of the file + Hash - Pointer to the buffer. The buffer is allocated by the routine. It must be NULL. +Return Value: + Returns STATUS_SUCCESS if no error occured. +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + BCRYPT_HASH_HANDLE hashHandle = NULL; + + // Test provided hash pointer and allocate it + if (NULL == Hash || NULL != *Hash) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasGetFileNameHash: Non null hash buffer\n")); + status = STATUS_UNSUCCESSFUL; + goto cleanup; + } + + if (NULL == (*Hash = ExAllocatePoolZero( + NonPagedPool, + KeysasData.HashLength, + KEYSAS_MEMORY_TAG + ))) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasGetFileNameHash: Failed to allocate output hash\n")); + status = STATUS_UNSUCCESSFUL; + goto cleanup; + } + + // Create the hash + if (!NT_SUCCESS(status = BCryptCreateHash( + KeysasData.HashProvider, + &hashHandle, + NULL, + 0, + NULL, + 0, + 0 + ))) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasGetFileNameHash: Failed to create hash\n")); + status = STATUS_UNSUCCESSFUL; + goto cleanup; + } + + // Feed the hash with FileName + if (!NT_SUCCESS(status = BCryptHashData( + hashHandle, + (PUCHAR) FileName->Buffer, + FileName->Length, + 0 + ))) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasGetFileNameHash: Failed to run hash\n")); + status = STATUS_UNSUCCESSFUL; + goto cleanup; + } + + // Finalize the hash in the output buffer + if (!NT_SUCCESS(status = BCryptFinishHash( + hashHandle, + *Hash, + KeysasData.HashLength, + 0 + ))) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasGetFileNameHash: Failed to finalize hash\n")); + status = STATUS_UNSUCCESSFUL; + goto cleanup; + } + +cleanup: + if (hashHandle) { + BCryptDestroyHash(hashHandle); + } + /* + if (NULL != hashObject) { + ExFreePoolWithTag(hashObject, KEYSAS_MEMORY_TAG); + } + */ + return status; } NTSTATUS KeysasScanFileInUserMode( _In_ PUNICODE_STRING FileName, + _In_ PUCHAR FileID, _In_ KEYSAS_FILTER_OPERATION Operation, - _Out_ PBOOLEAN SafeToOpen + _Out_ KEYSAS_AUTHORIZATION *Authorization ) /*++ Routine Description: @@ -531,7 +798,7 @@ Routine Description: Arguments: FileName - Name of the file. It should be NORMALIZED thus the complete path is given Operation - Operation code for the user app - SafeToOpen - Set to TRUE if the file is valid + Authorization - Authorization status granted by the service Return Value: The status of the operation, hopefully STATUS_SUCCESS. The common failure status will probably be STATUS_INSUFFICIENT_RESOURCES. @@ -544,8 +811,8 @@ Return Value: PAGED_CODE(); - // Set default authorization to true - *SafeToOpen = TRUE; + // Set default authorization to pending + *Authorization = AUTH_PENDING; KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasScanFileInUserMode: Entered\n")); @@ -554,7 +821,7 @@ Return Value: return status; } - if (FileName->Length > (KEYSAS_REQUEST_BUFFER_SIZE - 1)) { + if (FileName->Length > (KEYSAS_REQUEST_BUFFER_SIZE - KeysasData.HashLength - 1)) { KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasScanFileInUserMode: File name too long\n")); } @@ -570,13 +837,20 @@ Return Value: goto end; } + // Copy the File ID at the start of the request + memcpy(request->Content, FileID, KeysasData.HashLength); + // Copy the name of the file in the request - status = RtlStringCbCopyUnicodeString(request->Content, KEYSAS_REQUEST_BUFFER_SIZE * sizeof(WCHAR), FileName); + status = RtlStringCbCopyUnicodeString( + request->Content + KeysasData.HashLength/2, + KEYSAS_REQUEST_BUFFER_SIZE * sizeof(WCHAR) - KeysasData.HashLength, + FileName); if (STATUS_SUCCESS != status) { KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasScanFileInUserMode: Failed to convert UNICODE_STRING\n")); goto end; } request->Operation = Operation; + request->Operation = SCAN_FILE; replyLength = sizeof(*request); @@ -585,14 +859,14 @@ Return Value: KeysasData.Filter, &KeysasData.ClientPort, request, - FileName->Length+sizeof(KEYSAS_FILTER_OPERATION), + FileName->Length+sizeof(KEYSAS_FILTER_OPERATION) + KeysasData.HashLength, request, &replyLength, NULL ); if (STATUS_SUCCESS == status) { - *SafeToOpen = ((PKEYSAS_REPLY)request)->Result; + *Authorization = ((PKEYSAS_REPLY)request)->Result; KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasScanFileInUserMode: Received result\n")); } else { diff --git a/keysas-firewall/minifilter/keysasFile.h b/keysas-firewall/minifilter/keysasFile.h index 557ef74..d063a37 100644 --- a/keysas-firewall/minifilter/keysasFile.h +++ b/keysas-firewall/minifilter/keysasFile.h @@ -28,8 +28,16 @@ typedef struct _KEYSAS_FILE_CTX { // Authorization state of the file KEYSAS_AUTHORIZATION Authorization; + // SHA256 of the file name, used as a reference to perform context lookup + PUCHAR FileID; + // Lock used to protect the context PERESOURCE Resource; + + // Pointers to the context list + LIST_ENTRY FileCtxList; + + // TODO - Add reference to the volume so that it can be freed when the volume is released } KEYSAS_FILE_CTX, * PKEYSAS_FILE_CTX; #define KEYSAS_FILE_CTX_SIZE sizeof(KEYSAS_FILE_CTX) @@ -43,8 +51,9 @@ KfFileContextCleanup( NTSTATUS KeysasScanFileInUserMode( _In_ PUNICODE_STRING FileName, + _In_ PUCHAR FileID, _In_ KEYSAS_FILTER_OPERATION Operation, - _Out_ PBOOLEAN SafeToOpen + _Out_ KEYSAS_AUTHORIZATION* Authorization ); FLT_PREOP_CALLBACK_STATUS @@ -62,6 +71,13 @@ KfPostCreateHandler( _In_ FLT_POST_OPERATION_FLAGS Flags ); +FLT_PREOP_CALLBACK_STATUS +KfPreWriteHandler( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID* CompletionContext +); + NTSTATUS FindFileContext( _In_ PFLT_CALLBACK_DATA Data, @@ -69,4 +85,10 @@ FindFileContext( _Out_opt_ PBOOLEAN ContextCreated ); +NTSTATUS +KeysasGetFileNameHash( + _In_ PUNICODE_STRING FileName, + _Out_ PUCHAR *Hash +); + #endif \ No newline at end of file diff --git a/keysas-firewall/minifilter/keysasInstance.c b/keysas-firewall/minifilter/keysasInstance.c index 83d2875..20bd3ed 100644 --- a/keysas-firewall/minifilter/keysasInstance.c +++ b/keysas-firewall/minifilter/keysasInstance.c @@ -120,7 +120,7 @@ Return Value: } ExInitializeResourceLite(instanceContext->Resource); - // Attach the context to the file + // Attach the context to the instance status = FltSetInstanceContext( Instance, FLT_SET_CONTEXT_KEEP_IF_EXISTS, @@ -143,7 +143,7 @@ Return Value: status = STATUS_SUCCESS; } else { - // Successful creation of a new file context + // Successful creation of a new instance context KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!FindInstanceContext: Created a new instance context\n")); *ContextCreated = TRUE; } @@ -157,8 +157,7 @@ Return Value: NTSTATUS KeysasScanInstanceInUserMode( _In_ PUNICODE_STRING InstanceName, - _In_ KEYSAS_FILTER_OPERATION Operation, - _Out_ PBOOLEAN SafeToOpen + _Out_ KEYSAS_AUTHORIZATION* Authorization ) /*++ Routine Description: @@ -166,8 +165,7 @@ Routine Description: instance and tell our caller whether it's safe to open it. Arguments: FileName - Name of the file. It should be NORMALIZED thus the complete path is given - Operation - Operation code for the user app - SafeToOpen - Set to TRUE if the instance is valid + Authorization - Authorization status granted by the service Return Value: The status of the operation, hopefully STATUS_SUCCESS. The common failure status will probably be STATUS_INSUFFICIENT_RESOURCES. @@ -181,7 +179,7 @@ Return Value: PAGED_CODE(); // Set default authorization to true - *SafeToOpen = TRUE; + *Authorization = AUTH_ALLOW_ALL; KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasScanInstanceInUserMode: Entered\n")); @@ -212,7 +210,7 @@ Return Value: KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasScanInstanceInUserMode: Failed to convert UNICODE_STRING\n")); goto end; } - request->Operation = Operation; + request->Operation = SCAN_USB; replyLength = sizeof(*request); @@ -228,8 +226,10 @@ Return Value: ); if (STATUS_SUCCESS == status) { - *SafeToOpen = ((PKEYSAS_REPLY)request)->Result; - KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasScanInstanceInUserMode: Received result\n")); + *Authorization = ((PKEYSAS_REPLY)request)->Result; + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasScanInstanceInUserMode: Received result %x\n", + *Authorization)); + *Authorization = AUTH_ALLOW_WARNING; } else { KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KeysasScanInstanceInUserMode: Failed to send request to userspace\n")); @@ -295,7 +295,6 @@ Return Value: BOOLEAN instanceCreated = FALSE; wchar_t nameBuffer[512] = { 0 }; UNICODE_STRING volumeName = { 0, sizeof(nameBuffer)-sizeof(wchar_t), nameBuffer}; - BOOLEAN instanceValid = TRUE; // Print debug info on the call context KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfInstanceSetup: Entered\n")); @@ -395,7 +394,6 @@ Return Value: goto end; } - // TODO: default set instance to ALLOW AcquireResourceWrite(instanceContext->Resource); status = FltGetVolumeName(FltObjects->Volume, &volumeName, NULL); @@ -410,8 +408,7 @@ Return Value: status = KeysasScanInstanceInUserMode( &volumeName, - SCAN_USB, - &instanceValid + &instanceContext->Authorization ); if (!NT_SUCCESS(status)) { @@ -420,9 +417,9 @@ Return Value: goto end; } - instanceContext->Authorization = AUTH_ALLOW_WARNING; ReleaseResource(instanceContext->Resource); + status = STATUS_SUCCESS; KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas!KfInstanceSetup: Instance context attached\n")); } else { diff --git a/keysas-firewall/minifilter/keysasInstance.h b/keysas-firewall/minifilter/keysasInstance.h index d4a9f9e..382effb 100644 --- a/keysas-firewall/minifilter/keysasInstance.h +++ b/keysas-firewall/minifilter/keysasInstance.h @@ -76,9 +76,8 @@ FindInstanceContext( NTSTATUS KeysasScanInstanceInUserMode( - _In_ PUNICODE_STRING FileName, - _In_ KEYSAS_FILTER_OPERATION Operation, - _Out_ PBOOLEAN SafeToOpen + _In_ PUNICODE_STRING InstanceName, + _Out_ KEYSAS_AUTHORIZATION* Authorization ); #endif _H_KEYSAS_INSTANCE_ \ No newline at end of file diff --git a/keysas-firewall/tray-app/package-lock.json b/keysas-firewall/tray-app/package-lock.json index ac52dde..5bc2937 100644 --- a/keysas-firewall/tray-app/package-lock.json +++ b/keysas-firewall/tray-app/package-lock.json @@ -1880,9 +1880,9 @@ } }, "node_modules/rollup": { - "version": "3.20.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.20.6.tgz", - "integrity": "sha512-2yEB3nQXp/tBQDN0hJScJQheXdvU2wFhh6ld7K/aiZ1vYcak6N/BKjY1QrU6BvO2JWYS8bEs14FRaxXosxy2zw==", + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.0.tgz", + "integrity": "sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -2163,14 +2163,14 @@ } }, "node_modules/vite": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.0.tgz", - "integrity": "sha512-JTGFgDh3dVxeGBpuQX04Up+JZmuG6wu9414Ei36vQzaEruY/M4K0AgwtuB2b4HaBgB7R8l+LHxjB0jcgz4d2qQ==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", + "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", "dev": true, "dependencies": { "esbuild": "^0.17.5", - "postcss": "^8.4.21", - "rollup": "^3.20.2" + "postcss": "^8.4.23", + "rollup": "^3.21.0" }, "bin": { "vite": "bin/vite.js" diff --git a/keysas-firewall/tray-app/src-tauri/Cargo.toml b/keysas-firewall/tray-app/src-tauri/Cargo.toml index a6bf3c2..e21dc35 100644 --- a/keysas-firewall/tray-app/src-tauri/Cargo.toml +++ b/keysas-firewall/tray-app/src-tauri/Cargo.toml @@ -14,15 +14,14 @@ edition = "2021" tauri-build = { version = "1.2", features = [] } [dependencies] -tauri = { version = "1.2.5", features = ["notification-all", "shell-open", "system-tray"] } -tauri-plugin-positioner = {version = "1.0", features = ["system-tray"] } +tauri = { version = "1.2.5", features = ["dialog-message", "notification-all", "shell-open", "system-tray"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" anyhow = "1.0" # Bumped down simple logger version to avoid conflict with time dependency of tauri simple_logger = "4.1" log = "0.4" -tokio = {version = "1.28", features = ["net", "time"] } +libmailslot = {path = "../../libmailslot"} [dependencies.windows-sys] version = "0.48.0" diff --git a/keysas-firewall/tray-app/src-tauri/src/app_controler.rs b/keysas-firewall/tray-app/src-tauri/src/app_controler.rs deleted file mode 100644 index 591f3b2..0000000 --- a/keysas-firewall/tray-app/src-tauri/src/app_controler.rs +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * - * (C) Copyright 2019-2023 Luc Bonnafoux, Stephane Neveu - * - */ - -//! Controler for the application - -#![warn(unused_extern_crates)] -#![forbid(non_shorthand_field_patterns)] -#![warn(dead_code)] -#![warn(missing_debug_implementations)] -#![warn(missing_copy_implementations)] -#![warn(trivial_casts)] -#![warn(trivial_numeric_casts)] -#![warn(unused_extern_crates)] -#![warn(unused_import_braces)] -#![warn(unused_qualifications)] -#![warn(variant_size_differences)] -#![forbid(private_in_public)] -#![warn(overflowing_literals)] -#![warn(deprecated)] -#![warn(unused_imports)] - -use crate::filter_store::{FilterStore, KeysasAuthorization, USBDevice}; - -pub struct AppControler { - pub store: FilterStore -} - -impl AppControler { - pub fn init() -> AppControler { - let mut ctrl = AppControler { - store: FilterStore::init_store(), - }; - - // Create a default USB device for the tests - let usb = USBDevice { - name: String::from("Kingston USB"), - path: String::from("D:"), - authorization: KeysasAuthorization::AllowedRead, - files: Vec::new(), - }; - - ctrl.store.add_device(&usb); - - ctrl - } -} diff --git a/keysas-firewall/tray-app/src-tauri/src/app_controller.rs b/keysas-firewall/tray-app/src-tauri/src/app_controller.rs new file mode 100644 index 0000000..9356693 --- /dev/null +++ b/keysas-firewall/tray-app/src-tauri/src/app_controller.rs @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * + * (C) Copyright 2019-2023 Luc Bonnafoux, Stephane Neveu + * + */ + +//! Controller for the application + +#![warn(unused_extern_crates)] +#![forbid(non_shorthand_field_patterns)] +#![warn(dead_code)] +#![warn(missing_debug_implementations)] +#![warn(missing_copy_implementations)] +#![warn(trivial_casts)] +#![warn(trivial_numeric_casts)] +#![warn(unused_extern_crates)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] +#![warn(variant_size_differences)] +#![forbid(private_in_public)] +#![warn(overflowing_literals)] +#![warn(deprecated)] +#![warn(unused_imports)] + +use crate::filter_store::{FileAuth, FilterStore, USBDevice}; +use crate::service_if::{ServiceIf, FileUpdateMessage, KeysasAuthorization}; + +use anyhow::anyhow; +use std::sync::{Arc, RwLock}; +use tauri::{AppHandle, Manager}; + +/// Application controller object, it contains handle to the application main services +pub struct AppController { + pub store: RwLock, + view: AppHandle, + comm: ServiceIf +} + +impl AppController { + /// Initialize the application controller + /// It initialize + /// - the application data store + /// - the communication interface to the Keysas service + /// - store an handle to the view + pub fn init(app_handle: AppHandle) -> Result, anyhow::Error> { + // Create the application controller + let ctrl = Arc::new(AppController { + store: RwLock::new(FilterStore::init_store()), + view: app_handle, + comm: ServiceIf::init_service_if()? + }); + + // Start the server thread + if let Err(e) = ctrl.comm.start_server(&ctrl) { + log::error!("Failed to start communications with service: {e}"); + return Err(anyhow!("Failed to start server thread: {e}")); + } + + // Create a default USB device for the tests + let usb = USBDevice { + name: String::from("Kingston USB"), + path: String::from("D:"), + authorization: KeysasAuthorization::AuthAllowRead, + }; + + match ctrl.store.write() { + Ok(mut store) => store.add_device(&usb), + Err(e) => log::error!("Failed to get store lock: {e}"), + } + + Ok(ctrl) + } + + /// Called when a file notification has been received from the driver + /// It adds the new file to the data store and notifies the view to update itself + pub fn notify_file_change(&self, update: &FileUpdateMessage) { + let mut id: [u16; 16] = Default::default(); + id.copy_from_slice(&update.id); + + // Store the new file + let file = FileAuth { + device: String::from(&update.device), + id, + path: String::from(&update.path), + authorization: update.authorization.to_u8_file(), + }; + + match self.store.write() { + Ok(mut store) => { + if let Err(e) = store.add_file(&file) { + println!("Failed to add file in store: {e}"); + return; + } + } + Err(e) => println!("Failed to get store lock: {e}"), + } + + // Notify the GUI to update the view + if let Err(e) = self + .view + .emit_all("file_update", String::from(&update.device)) + { + println!("Failed to notify view of file changed: {e}"); + } + } + + /// Return the list of files in the datastore + pub fn get_file_list(&self, device_path: &str) -> Result, anyhow::Error> { + match self.store.read() { + Ok(store) => store.get_files(device_path), + Err(e) => return Err(anyhow!("Failed to get store lock: {e}")), + } + } + + /// Request a change of file authorization in the driver + /// If it is successful it then change it in the datastore and updates the view + pub fn request_file_auth_toggle(&self, device: &str, id: &[u16], path: &str, + new_auth: KeysasAuthorization) -> Result<(), anyhow::Error> { + + let mut file_id: [u16; 16] = Default::default(); + file_id.copy_from_slice(&id); + + if let Err(e) = self.comm.send_msg(&FileUpdateMessage{ + device: device.to_string(), + id: file_id, + path: path.to_string(), + authorization: new_auth}) { + println!("request_file_auth_toggle: File toggle failed: {e}"); + return Err(anyhow!("Failed to send request to Keysas daemon: {e}")); + } + + Ok(()) + } +} \ No newline at end of file diff --git a/keysas-firewall/tray-app/src-tauri/src/filter_store.rs b/keysas-firewall/tray-app/src-tauri/src/filter_store.rs index 6f7ff36..ef7e779 100644 --- a/keysas-firewall/tray-app/src-tauri/src/filter_store.rs +++ b/keysas-firewall/tray-app/src-tauri/src/filter_store.rs @@ -23,20 +23,15 @@ #![warn(deprecated)] #![warn(unused_imports)] -use anyhow::anyhow; +use serde::Serialize; +use crate::service_if::KeysasAuthorization; -#[derive(Debug, Clone)] -pub enum KeysasAuthorization { - Blocked, - AllowedRead, - AllowedRwWarning, - AllowedAll, -} - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct FileAuth { + pub device: String, + pub id: [u16; 16], pub path: String, - pub authorization: KeysasAuthorization, + pub authorization: u8, } #[derive(Debug, Clone)] @@ -44,18 +39,19 @@ pub struct USBDevice { pub name: String, pub path: String, pub authorization: KeysasAuthorization, - pub files: Vec, } #[derive(Debug, Clone)] pub struct FilterStore { pub devices: Vec, + pub files: Vec, } impl FilterStore { pub fn init_store() -> FilterStore { FilterStore { devices: Vec::new(), + files: Vec::new(), } } @@ -63,40 +59,61 @@ impl FilterStore { self.devices.push(device.clone()); } - pub fn remove_device(&mut self, device_name: &str) -> Result<(), anyhow::Error> { - Ok(()) + pub fn remove_device(&mut self, _device_name: &str) -> Result<(), anyhow::Error> { + todo!() } pub fn get_devices(&self) -> &[USBDevice] { &self.devices } + pub fn get_device(&self, device_path: &str) -> Option<&USBDevice> { + self.devices.iter().find(|&d| d.path.eq(device_path)) + } + + pub fn get_device_mut(&mut self, device_path: &str) -> Option<&mut USBDevice> { + self.devices.iter_mut().find(|d| d.path.eq(device_path)) + } + pub fn set_device_auth( &mut self, - device_name: &str, - auth: KeysasAuthorization, + _device_name: &str, + _auth: KeysasAuthorization, ) -> Result<(), anyhow::Error> { - Ok(()) + todo!() } - pub fn add_file(&mut self, device_name: &str, file: &FileAuth) -> Result<(), anyhow::Error> { + pub fn add_file(&mut self, file: &FileAuth) -> Result<(), anyhow::Error> { + self.files.push(file.clone()); Ok(()) } - pub fn remove_file(&mut self, device_name: &str, file_name: &str) -> Result<(), anyhow::Error> { - Ok(()) + pub fn remove_file( + &mut self, + _device_name: &str, + _file_name: &str, + ) -> Result<(), anyhow::Error> { + todo!() } - pub fn get_files(&self, device_name: &str) -> Result<&[FileAuth], anyhow::Error> { - Err(anyhow!("Not implemented")) + pub fn get_files(&self, device_path: &str) -> Result, anyhow::Error> { + let files: Vec = self + .files + .iter() + .filter_map(|f| match f.device.eq(device_path) { + true => Some(f.clone()), + false => None, + }) + .collect(); + Ok(files) } pub fn set_file_auth( &mut self, - device_name: &str, - file_name: &str, - auth: KeysasAuthorization, + _device_name: &str, + _file_name: &str, + _auth: KeysasAuthorization, ) -> Result<(), anyhow::Error> { - Ok(()) + todo!() } } diff --git a/keysas-firewall/tray-app/src-tauri/src/main.rs b/keysas-firewall/tray-app/src-tauri/src/main.rs index d2108bb..8202fa6 100644 --- a/keysas-firewall/tray-app/src-tauri/src/main.rs +++ b/keysas-firewall/tray-app/src-tauri/src/main.rs @@ -25,19 +25,20 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -//mod service_if; -mod app_controler; +mod service_if; +mod app_controller; mod filter_store; use tauri::{ - AppHandle, Manager, SystemTray, SystemTrayEvent, State + AppHandle, Manager, SystemTray, SystemTrayEvent, State, PhysicalPosition, + LogicalPosition, LogicalSize, Window }; -use tauri_plugin_positioner::{Position, WindowExt}; - -use crate::app_controler::AppControler; - +use std::sync::Arc; use anyhow::anyhow; +use crate::app_controller::AppController; +use crate::service_if::KeysasAuthorization; + /// Payload for the init event sent to the usb_details window #[derive(Clone, serde::Serialize)] struct InitPayload { @@ -58,23 +59,20 @@ fn main() -> Result<(), anyhow::Error> { /// Initialize the tauri application as a system tray app fn init_tauri() -> Result<(), anyhow::Error> { let app = tauri::Builder::default() - .plugin(tauri_plugin_positioner::init()) - .manage(AppControler::init()) + .setup(|app| { + app.manage(AppController::init(app.handle())?); + Ok(()) + }) .system_tray(SystemTray::new()) - .on_system_tray_event(|app, event| match event { - SystemTrayEvent::LeftClick { - position: _, - size: _, - .. - } => { - if let Err(e) = open_usb_view(app) { + .on_system_tray_event(|app, event| + if let SystemTrayEvent::LeftClick {position, .. } = event { + if let Err(e) = open_usb_view(app, &position) { log::error!("Failed to open main view: {e}"); app.exit(1); } } - _ => {} - }) - .invoke_handler(tauri::generate_handler![get_file_list]) + ) + .invoke_handler(tauri::generate_handler![get_file_list, toggle_file_auth]) .build(tauri::generate_context!())?; app.run(|_app_handle, event| match event { @@ -87,19 +85,58 @@ fn init_tauri() -> Result<(), anyhow::Error> { Ok(()) } +/// Set the application on the bottom right corner over the desktop tray +/// +/// # Arguments +/// +/// * 'w' - Reference to the window +/// * 'click' - Position of the click event, it corresponds to the top of the icon in the tray +fn set_window_over_tray(w: &Window, click: &PhysicalPosition) + -> Result<(), anyhow::Error> { + let screen = w.current_monitor()?.ok_or_else(|| anyhow!("Not screen detected"))?; + let scale_factor = screen.scale_factor(); + + // Click position corresponds to the top left corner of the icon + // Convert the click physical position to logical + let click_log = click.to_logical::(scale_factor); + + let screen_pos_log = screen.position().to_logical::(scale_factor); + let screen_size_log = screen.size().to_logical::(scale_factor); + + // Set arbitrary size for the window + // TODO: adapt it to the monitor scale factor + let window_size = LogicalSize::::new(400.0, 300.0); + w.set_size(window_size)?; + + // Set the position of the window just above the click position and the farthest to the right + let x_log = if click_log.x+window_size.width <= screen_pos_log.x+screen_size_log.width { + click_log.x + } else { + screen_pos_log.x + screen_size_log.width - window_size.width + }; + let window_pos = LogicalPosition::::new( + x_log, + click_log.y-window_size.height + ); + w.set_position(window_pos)?; + + Ok(()) +} + /// Toggle the USB view when the tray icon is clicked /// /// # Arguments /// /// * 'app' - The tauri application -fn open_usb_view(app: &AppHandle) -> Result<(), anyhow::Error> { +fn open_usb_view(app: &AppHandle, click: &PhysicalPosition) + -> Result<(), anyhow::Error> { // Get the window match app.get_window("main") { Some(w) => { // If the window exists, toggle its visibility match w.is_visible()? { false => { - w.move_window(Position::BottomRight)?; + set_window_over_tray(&w, click)?; w.set_focus()?; w.show()?; } @@ -114,20 +151,65 @@ fn open_usb_view(app: &AppHandle) -> Result<(), anyhow::Error> { app, "main", tauri::WindowUrl::App("index.html".into()) - ).build()?; - w.move_window(Position::BottomRight)?; - w.set_decorations(false)?; - w.set_focus()?; + ) + .decorations(false) + .focused(true) + .build()?; + set_window_over_tray(&w, click)?; } }; Ok(()) } +/// Command to retrieve list of all the files in a USB device +/// The list is returned as a json array of File object as follows +/// [{ +/// device: string, +/// path: string +/// authorization: boolean +/// }, ..] +/// +/// # Arguments +/// +/// * 'device_path' - Name of the path of the volume, e.g 'D:' +/// * 'app_ctrl' - Handle to the application controler, it is supplied by tauri +#[tauri::command] +async fn get_file_list(device_path: String, app_ctrl: State<'_, Arc>) -> Result { + match app_ctrl.get_file_list(&device_path) { + Ok(files) => { + match serde_json::to_string(&files) { + Ok(s) => { + return Ok(s); + }, + Err(e) => { + log::error!("Failed to serialize result: {e}"); + return Err(String::from("Failed to get files")); + } + } + }, + Err(e) => { + log::error!("Device not found: {e}"); + return Err(String::from("Failed to get files")); + } + } +} + +/// Request to toggle the authorization for a file in a give device +/// +/// # Arguments +/// +/// * 'device' - Name of the USB device volume, e.g. 'D:' +/// * 'path' - Full path to the file on the device +/// * 'current_auth' - Current authorization status for the file #[tauri::command] -fn get_file_list(usb_name: String, app_ctrl: State) -> Result { - match app_ctrl.store.get_files(&usb_name) { - Ok(files) => Ok(String::from("Found files")), - Err(e) => Err(String::from("Failed to get files")) +async fn toggle_file_auth(device: String, id: [u16; 16], path: String, new_auth: u8, + app_ctrl: State<'_, Arc>) -> Result<(), String> { + let auth = KeysasAuthorization::from_u8_file(new_auth); + println!("Test"); + if let Err(e) = app_ctrl.request_file_auth_toggle(&device, &id, &path, auth) { + println!("toggle_file_auth: File toggle failed: {e}"); + return Err(e.to_string()); } + Ok(()) } \ No newline at end of file diff --git a/keysas-firewall/tray-app/src-tauri/src/service_if.rs b/keysas-firewall/tray-app/src-tauri/src/service_if.rs index d7564fc..59642af 100644 --- a/keysas-firewall/tray-app/src-tauri/src/service_if.rs +++ b/keysas-firewall/tray-app/src-tauri/src/service_if.rs @@ -23,48 +23,118 @@ #![warn(deprecated)] #![warn(unused_imports)] -use std::time::Duration; -use std::io; -use std::thread; -use tokio::net::windows::named_pipe::ClientOptions; -use tokio::time; -use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY; use anyhow::anyhow; +use serde::{Serialize, Deserialize}; +use std::sync::{Arc, RwLock}; +use libmailslot; -use crate::filter_store::FilterStore; +use crate::app_controller::AppController; -const SERVICE_PIPE: &str = r"\\.\pipe\keysas-service"; +/// Authorization states for files and USB devices +#[derive(Debug, Deserialize, Serialize, Clone, Copy)] +pub enum KeysasAuthorization { + /// Default value + AuthUnknown = 0, + /// Authorization request pending + AuthPending, + /// Access is blocked + AuthBlock, + /// Access is allowed in read mode only + AuthAllowRead, + /// Access is allowed with a warning to the user + AuthAllowWarning, + /// Access is allowed for all operations + AuthAllowAll +} -/// Initialize the pipe with Keysas Service and start a thread to monitor it -pub fn init_service_if(store: &FilterStore) -> Result<(), anyhow::Error> { - // Initialize the client socket - let client = loop { - match ClientOptions::new().open(SERVICE_PIPE) { - Ok(client) => break client, - Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => (), - Err(e) => return Err(anyhow!("Failed to open client socket")), +impl KeysasAuthorization { + /// Convert the authorization enum to an unsigned char so that it can be send to javascript + pub fn to_u8_file(&self) -> u8 { + match self { + Self::AuthAllowRead => 1, + Self::AuthAllowAll => 2, + _ => 0 } - time::sleep(Duration::from_millis(50)); - }; + } - // Start the listening thread - tokio::task::spawn(async { - let mut msg = vec![0;1024]; + /// Convert an unsigned char to the authorization enum for a file + pub fn from_u8_file(auth: u8) -> KeysasAuthorization { + match auth { + 1 => Self::AuthAllowRead, + 2 => Self::AuthAllowAll, + _ => Self::AuthBlock + } + } +} + +/// Message for a file status notification +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct FileUpdateMessage { + pub device: String, + pub id: [u16; 16], + pub path: String, + pub authorization: KeysasAuthorization +} + +/// Handle to the service interface client and server +pub struct ServiceIf { + server: Arc> +} + +/// Name of the communication pipe +const SERVICE_PIPE: &str = r"\\.\mailslot\keysas\service-to-app"; +const TRAY_PIPE: &str = r"\\.\mailslot\keysas\app-to-service"; - loop { - client.readable().await; - match client.try_read(&mut msg) { - Ok(n) => { - msg.truncate(n); - println!("Message: {:?}", msg); - break; +impl ServiceIf { + /// Initialize the pipe with Keysas Service + pub fn init_service_if() -> Result { + // Initialize the mailslot handles + let server = match libmailslot::create_mailslot(SERVICE_PIPE) { + Ok(s) => s, + Err(e) => return Err(anyhow!("Failed to create server: {e}")), + }; + + Ok(ServiceIf{server: RwLock::new(server).into()}) + } + + /// Start the server thread to listen for the Keysas service + pub fn start_server(&self, ctrl: &Arc) -> Result<(), anyhow::Error> { + // Start listening on the server side + let ctrl_hdl = ctrl.clone(); + let server = self.server.clone(); + std::thread::spawn(move || { + // Get a mutable lock on the server + let server = match server.write() { + Ok(s) => s, + Err(_) => { + return; + } + }; + println!("Start listening for daemon"); + loop { + while let Ok(Some(msg)) = libmailslot::read_mailslot(&server) { + if let Ok(update) = serde_json::from_slice::(msg.as_bytes()) { + ctrl_hdl.notify_file_change(&update); + println!("message from service {:?}", update); + } } - Err(e) if e.kind() == io::ErrorKind::WouldBlock => {continue;} - Err(e) => {return;} + std::thread::sleep(std::time::Duration::from_secs(1)); } + }); + Ok(()) + } + + pub fn send_msg(&self, msg: &impl Serialize) -> Result<(), anyhow::Error> { + let msg_vec = match serde_json::to_string(msg) { + Ok(m) => m, + Err(e) => return Err(anyhow!("Failed to serialize message: {e}")) + }; + + if let Err(e) = libmailslot::write_mailslot(TRAY_PIPE, &msg_vec) { + return Err(anyhow!("Failed to post message to the mailslot: {e}")); } - }); - Ok(()) -} \ No newline at end of file + Ok(()) + } +} diff --git a/keysas-firewall/tray-app/src-tauri/tauri.conf.json b/keysas-firewall/tray-app/src-tauri/tauri.conf.json index 047f14c..95c3016 100644 --- a/keysas-firewall/tray-app/src-tauri/tauri.conf.json +++ b/keysas-firewall/tray-app/src-tauri/tauri.conf.json @@ -19,6 +19,9 @@ "shell": { "all": false, "open": true + }, + "dialog": { + "message": true } }, "bundle": { @@ -50,9 +53,7 @@ "title": "Keysas USB Firewall", "url": "index.html", "visible": false, - "decorations": false, - "width": 600, - "height": 400 + "decorations": false } ] } diff --git a/keysas-firewall/tray-app/src/App.vue b/keysas-firewall/tray-app/src/App.vue index 61bb4e9..000f548 100644 --- a/keysas-firewall/tray-app/src/App.vue +++ b/keysas-firewall/tray-app/src/App.vue @@ -1,62 +1,52 @@ @@ -64,7 +54,7 @@ import 'bootstrap-icons/font/bootstrap-icons.css' import {invoke} from "@tauri-apps/api" enum AuthorizationMode { - Blocked = 1, + Blocked = 0, Allowed_Read, Allowed_RW } @@ -76,8 +66,10 @@ declare interface UsbDevice { } declare interface File { + device: string, + id: number[], path: string, - allowed: boolean + authorization: AuthorizationMode } export default { @@ -93,27 +85,58 @@ export default { usb_device: {} as UsbDevice, } }, - mounted() { + async mounted() { this.usb_list.push({ name: "Kingston USB", - path: "D:/", + path: "D:", authorization: AuthorizationMode.Allowed_RW }); + + await listen('file_update', (event) => { + this.refreshFileList(event.payload as string); + }); }, methods: { - showUsbDevice(usb_device: UsbDevice) { + async refreshFileList(device_path: string) { + if (device_path === this.usb_device.path) { + invoke('get_file_list', {devicePath: device_path}) + .then((result) => { + const file_list = JSON.parse(result as string); + file_list.forEach((file: File) => { + if (!this.file_list.some(f => f.path === file.path)) { + this.file_list.push(file); + } + }); + }) + .catch((error) => console.error(error)); + } + }, + async showUsbDevice(usb_device: UsbDevice) { // Set the selected device this.usb_device = usb_device; // Fetch the file list from the backend - invoke('get_file_list') - .then((result) => console.log(result)) - .catch((error) => console.error(error)); + this.refreshFileList(usb_device.path); // Display the details window this.showUsbList = false; this.showUsbDetails = true; }, + async toggleFileAuth(file: File, new_mode: AuthorizationMode) { + let auth = 0; // Blocked + if (new_mode == AuthorizationMode.Allowed_Read) { + auth = 1; + } else if (new_mode == AuthorizationMode.Allowed_RW) { + auth = 2; + } + console.log("New authorization"); + invoke('toggle_file_auth', {device: file.device, id: file.id, path: file.path, newAuth: auth}) + .then((result) => { + console.log("New authorization result OK"); + file.authorization = new_mode; + }) + .catch((error) => alert("Toggle file authorization failed")); + }, backToUsbList() { this.showUsbDetails = false; this.showUsbList = true; @@ -124,4 +147,48 @@ export default { diff --git a/keysas-firewall/usbfilter/USBFilterDriver.c b/keysas-firewall/usbfilter/USBFilterDriver.c new file mode 100644 index 0000000..8aff8fd --- /dev/null +++ b/keysas-firewall/usbfilter/USBFilterDriver.c @@ -0,0 +1,517 @@ +/*++ + +Copyright (c) 2023 Luc Bonnafoux + +Module Name: + + USBFilterDriver.c + +Abstract: + + This filter monitors USB device connections. + +Environment: + + Kernel mode + +--*/ + +#include +#include + +/*--------------------------------------- +- +- Global definitions +- +-----------------------------------------*/ +#define KEYSAS_USBFILTER_POOL_TAG 'FUeK' + + +/*--------------------------------------- +- +- Function declarations +- +-----------------------------------------*/ + +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath +); + +NTSTATUS +KUFDeviceAddEvt( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit +); + +VOID +KUFEvtDeviceControl( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ size_t OutputBufferLength, + _In_ size_t InputBufferLength, + _In_ ULONG IoControlCode +); + +NTSTATUS +KUFPnpQueryDeviceCallback( + IN WDFDEVICE Device, + IN PIRP Irp +); + +NTSTATUS +KUFGetDeviceInfo( + _In_ PDEVICE_OBJECT Device, + _In_ BUS_QUERY_ID_TYPE Type, + _Outptr_opt_ PWCHAR* Information +); + +NTSTATUS +KUFIsUsbHub( + _In_ PDEVICE_OBJECT Device, + _Out_ PBOOLEAN IsHub +); + +IO_COMPLETION_ROUTINE KUFDeviceRelationsPostProcessing; + +#ifdef ALLOC_PRAGMA +#pragma alloc_text (INIT, DriverEntry) +#pragma alloc_text (PAGE, KUFDeviceAddEvt) +#pragma alloc_text (PAGE, KUFEvtDeviceControl) +#pragma alloc_text (PAGE, KUFPnpQueryDeviceCallback) +#pragma alloc_text (PAGE, KUFGetDeviceInfo) +#pragma alloc_text (PAGE, KUFIsUsbHub) +#endif + +/*--------------------------------------- +- +- Function implementations +- +-----------------------------------------*/ + +NTSTATUS +KUFGetDeviceInfo( + _In_ PDEVICE_OBJECT Device, + _In_ BUS_QUERY_ID_TYPE Type, + _Outptr_opt_ PWCHAR * Information +) +/*++ +Routine Description: + This routine is called to get information on a PDO + +Arguments: + Device - Pointer to the target device + Type - Type of information requested + Information - Output pointer for the information. It is allocated by the function + +Return Value: + Return STATUS_SUCCESS + +IRQL: + Must be called at PASSIVE_LEVEL +--*/ +{ + NTSTATUS result = STATUS_UNSUCCESSFUL; + KEVENT ke; + IO_STATUS_BLOCK ios = { 0 }; + PIRP irp = NULL; + PIO_STACK_LOCATION stack = NULL; + NTSTATUS nts = STATUS_UNSUCCESSFUL; + size_t bufferLength = 0; + + // Test inputs provided + if (NULL == Device || NULL == Information) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFGetDeviceInfo: Invalid inputs\n")); + goto cleanup; + } + + KeInitializeEvent(&ke, NotificationEvent, FALSE); + + irp = IoBuildSynchronousFsdRequest( + IRP_MJ_PNP, + Device, + NULL, + 0, + NULL, + &ke, + &ios + ); + + if (NULL == irp) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFGetDeviceInfo: Failed to allocate IRP\n")); + goto cleanup; + } + + irp->IoStatus.Status = STATUS_NOT_SUPPORTED; + + stack = IoGetNextIrpStackLocation(irp); + + if (NULL == stack) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFGetDeviceInfo: Failed to get stack location\n")); + goto cleanup; + } + + stack->MinorFunction = IRP_MN_QUERY_ID; + stack->Parameters.QueryId.IdType = Type; + + nts = IoCallDriver(Device, irp); + + if (STATUS_PENDING == nts) { + KeWaitForSingleObject(&ke, Executive, KernelMode, FALSE, NULL); + } + + if (NT_SUCCESS(nts)) { + bufferLength = (wcslen((WCHAR*)ios.Information)+1) * sizeof(WCHAR); + *Information = (PWCHAR)ExAllocatePool2(NonPagedPool, bufferLength, KEYSAS_USBFILTER_POOL_TAG); + if (NULL != *Information) { + RtlCopyMemory(*Information, (PWCHAR) ios.Information, bufferLength - 2); + result = STATUS_SUCCESS; + } + else { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFGetDeviceInfo: Failed to allocate output buffer\n")); + } + } + +cleanup: + + return result; +} + +NTSTATUS +KUFIsUsbHub( + _In_ PDEVICE_OBJECT Device, + _Out_ PBOOLEAN IsHub +) +/*++ +Routine Description: + This routine test if a physical device is a USB root Hub + The decision is made on the Device ID. For USB root hubs it starts with: + "USB\ROOT_HUB', "NUSB3\ROOT_HUB" or "IUSB3\ROOT_HUB" + TODO - Verify the exhaustivity of the list + +Arguments: + Device - Pointer to the device to test + IsHub - Boolean containing the result of the test + +Return: + Returns STATUS_SUCCESS if no error occured. + +IRQL: + Must be called at PASSIVE_LEVEL +--*/ +{ + NTSTATUS result = STATUS_UNSUCCESSFUL; + PWCHAR deviceId = NULL; + + // Test inputs + if (NULL == Device || NULL == IsHub) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFIsUsbHub: Invalid inputs\n")); + goto cleanup; + } + // Set default to FALSE + *IsHub = FALSE; + + // Get DeviceID + if (STATUS_SUCCESS != KUFGetDeviceInfo( + Device, + BusQueryDeviceID, + &deviceId + )) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFIsUsbHub: Failed to get Device ID\n")); + goto cleanup; + } + + // Compare with reference strings + if (!wcsncmp(deviceId, L"USB\\ROOT_HUB", 13) + || !wcsncmp(deviceId, L"NUSB3\\ROOT_HUB", 15) + || !wcsncmp(deviceId, L"IUSB3\\ROOT_HUB", 15)) { + // It is a USB Hub + *IsHub = TRUE; + } + + result = STATUS_SUCCESS; + +cleanup: + if (NULL != deviceId) { + ExFreePoolWithTag(deviceId, KEYSAS_USBFILTER_POOL_TAG); + } + + return result; +} + +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath +) +/*++ +Routine Description: + This is the initialization routine for this Keysas driver. + +Arguments: + DriverObject - Pointer to driver object created by the system to + represent this driver. + RegistryPath - Unicode string identifying where the parameters for this + driver are located in the registry. + +Return Value: + Returns STATUS_SUCCESS. + +IRQL: + Routine called at PASSIVE_LEVEL in system thread context. +--*/ +{ + WDF_DRIVER_CONFIG config = { 0 }; + + NTSTATUS status = STATUS_SUCCESS; + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!DriverEntry: Entered\n")); + + WDF_DRIVER_CONFIG_INIT(&config, KUFDeviceAddEvt); + + status = WdfDriverCreate( + DriverObject, + RegistryPath, + WDF_NO_OBJECT_ATTRIBUTES, + &config, + WDF_NO_HANDLE + ); + + if (!NT_SUCCESS(status)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!DriverEntry: WdfDriverCreate failed with status: %0x8x\n", + status)); + } + + status = STATUS_SUCCESS; + + return status; +} + +NTSTATUS +KUFDeviceAddEvt( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit +) +/*++ +Routine Description: + Called by the system when a new device is found + +Arguments: + Driver - Pointer to our driver + DeviceInit - New device initialization structure + +Return Value: + Returns STATUS_SUCCESS. + +IRQL: + Routine called at PASSIVE_LEVEL in system thread context. +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + WDFDEVICE wdfDevice = { 0 }; + WDF_IO_QUEUE_CONFIG ioQueueConfig = { 0 }; + WDF_OBJECT_ATTRIBUTES wdfObjectAttr = { 0 }; + UCHAR minorFunctions = 0; + + UNREFERENCED_PARAMETER(Driver); + + PAGED_CODE(); + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFDeviceAddEvt: Entered\n")); + + // Set the new instance as a filter + WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_BUS_EXTENDER); + WdfFdoInitSetFilter(DeviceInit); + + minorFunctions = IRP_MN_QUERY_DEVICE_RELATIONS; + status = WdfDeviceInitAssignWdmIrpPreprocessCallback( + DeviceInit, + KUFPnpQueryDeviceCallback, + IRP_MJ_PNP, + &minorFunctions, + 1 + ); + + if (!NT_SUCCESS(status)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFDeviceAddEvt: WdfDeviceInitAssignWdmIrpPreprocessCallback failed with status: %0x8x\n", + status)); + goto cleanup; + } + + WDF_OBJECT_ATTRIBUTES_INIT(&wdfObjectAttr); + + // Create the new instance for the device + status = WdfDeviceCreate( + &DeviceInit, + &wdfObjectAttr, + &wdfDevice + ); + + if (!NT_SUCCESS(status)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFDeviceAddEvt: WdfDeviceCreate failed with status: %0x8x\n", + status)); + goto cleanup; + } + + // Create a queue to handle the requests + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE( + &ioQueueConfig, + WdfIoQueueDispatchParallel + ); + + ioQueueConfig.EvtIoDeviceControl = KUFEvtDeviceControl; + + status = WdfIoQueueCreate( + wdfDevice, + &ioQueueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + WDF_NO_HANDLE + ); + + if (!NT_SUCCESS(status)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFDeviceAddEvt: WdfIoQueueCreate failed with status: %0x8x\n", + status)); + goto cleanup; + } + + status = STATUS_SUCCESS; + +cleanup: + + return status; +} + +VOID +KUFEvtDeviceControl( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ size_t OutputBufferLength, + _In_ size_t InputBufferLength, + _In_ ULONG IoControlCode +) +/*++ +Routine Description: + Handler for I/O request to the device + +Arguments: + Queue - Pointer to the framework queue + Request - Pointer to the request + OutputBufferLength - Length, in bytes, of the request's output buffer + InputBufferLength - Length, in bytes, of the request's input buffer + IoControlCode - IOCTL associated with the request + +Return Value: + Returns STATUS_SUCCESS. + +IRQL: + Can be called at IRQL <= DISPATCH_LEVEL +--*/ +{ + WDF_REQUEST_SEND_OPTIONS sendOpts = { 0 }; + WDFDEVICE wdfDevice = NULL; + NTSTATUS status; + + UNREFERENCED_PARAMETER(OutputBufferLength); + UNREFERENCED_PARAMETER(InputBufferLength); + UNREFERENCED_PARAMETER(IoControlCode); + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFEvtDeviceControl: Entered\n")); + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFEvtDeviceControl: Request 0x%p - IoControlCode 0x%p\n", + Request, IoControlCode)); + + WDF_REQUEST_SEND_OPTIONS_INIT(&sendOpts, WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET); + + wdfDevice = WdfIoQueueGetDevice(Queue); + + if (!WdfRequestSend(Request, WdfDeviceGetIoTarget(wdfDevice), &sendOpts)) { + status = WdfRequestGetStatus(Request); + WdfRequestComplete(Request, STATUS_SUCCESS); + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFEvtDeviceControl: WdfRequestSend failed\n")); + } + +} + +_Use_decl_annotations_ +NTSTATUS +KUFDeviceRelationsPostProcessing( + PDEVICE_OBJECT Device, + PIRP Irp, + PVOID Context +) +/*++ +Routine Description: + Completion routine called to filter device relations list provided by USB hubs to the PNP Manager + +Arguments: + Device - Pointer to the device object + Irp - Pointer to the IRP for the current request + Context - Additional data passed in the pre process stage, not used +--*/ +{ + NTSTATUS result = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Irp); + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFDeviceRelationsPostProcessing: Entered\n")); + + return result; +} + +NTSTATUS +KUFPnpQueryDeviceCallback( + IN WDFDEVICE Device, + IN PIRP Irp +) +/*++ +Routine Description: + This routine is called when a IRP_MN_QUERY_DEVICE_RELATIONS is received + +Arguments: + Device - Pointer to the device object for this device + Irp - Pointer to the IRP for the current request + +Return Value: + The function value is the final status of the call + +IRQL: + Is called at the IRQL of the IRP calling thread +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PIO_STACK_LOCATION irpStack = NULL; + + KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Keysas - USBFilter!KUFPnpQueryDeviceCallback: Entered\n")); + irpStack = IoGetCurrentIrpStackLocation(Irp); + + if (NULL != irpStack + && BusRelations == irpStack->Parameters.QueryDeviceRelations.Type) { + // Register a callback to filter the list of devices returned by the hub + IoCopyCurrentIrpStackLocationToNext(Irp); + + IoSetCompletionRoutine( + Irp, + KUFDeviceRelationsPostProcessing, + NULL, // No context + TRUE, // Call on successful IRP + FALSE, // Don't invoke on error + FALSE // Dont't invoke on canceled IRP + ); + } + else { + // No callback for post processing the IRP + // Return the IRP to the framework + IoSkipCurrentIrpStackLocation(Irp); + } + + status = WdfDeviceWdmDispatchPreprocessedIrp( + Device, + Irp + ); + + return status; +} \ No newline at end of file diff --git a/keysas-firewall/usbfilter/usbfilter.inf b/keysas-firewall/usbfilter/usbfilter.inf new file mode 100644 index 0000000..dfee57f --- /dev/null +++ b/keysas-firewall/usbfilter/usbfilter.inf @@ -0,0 +1,47 @@ +; +; usbfilter.inf +; + +[Version] +Signature="$WINDOWS NT$" +Class = USB +ClassGuid = {36fc9e60-c465-11cf-8056-444553540000} +Provider=%ManufacturerName% +CatalogFile=usbfilter.cat +DriverVer = 05/10/2023,16.21.56.700 +PnpLockdown=0 + +[DefaultInstall.NTamd64] +CopyFiles=@usbfilter.sys +Addreg = usbfilter.AddReg + +[DestinationDirs] +DefaultDestDir = 12 + +[usbfilter.AddReg] +HKLM, System\CurrentControlSet\Control\Class\{36fc9e60-c465-11cf-8056-444553540000}, UpperFilters, 0x00010008, usbfilter + +[DefaultInstall.NTamd64.Services] +AddService = usbfilter, , usbfilter.Service.Install + +[usbfilter.Service.Install] +DisplayName = "usbfilter" +Description = "Keysas USB filter" +ServiceBinary = %12%\usbfilter.sys +ServiceType = 1 +StartType = 0 +ErrorControl = 1 + +[SourceDisksFiles] +usbfilter.sys=1 + +[SourceDisksNames] +1 = %DiskId1% + +[Strings] +; SPSVCINST_ASSOCSERVICE= 0x00000002 +ManufacturerName="Keysas" +DiskName = "usbfilter Installation Disk" +usbfilter.DeviceDesc = "usbfilter Device" +usbfilter.SVCDESC = "usbfilter Service" +DiskId1 = "USB upper filter driver" \ No newline at end of file diff --git a/keysas-firewall/usbfilter/usbfilter.vcxproj b/keysas-firewall/usbfilter/usbfilter.vcxproj new file mode 100644 index 0000000..dcb5e08 --- /dev/null +++ b/keysas-firewall/usbfilter/usbfilter.vcxproj @@ -0,0 +1,127 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + {A1988889-1DC2-4E8A-B301-3094926F732E} + {1bc93793-694f-48fe-9372-81e2b05556fd} + v4.5 + 12.0 + Debug + x64 + usbfilter + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + false + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + false + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + + + + + + + + + DbgengKernelDebugger + C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\km\x86;C:\Program Files (x86)\Windows Kits\10\Lib\wdf\kmdf\x86\1.33;$(LibraryPath) + + + DbgengKernelDebugger + C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\km\x86;C:\Program Files (x86)\Windows Kits\10\Lib\wdf\kmdf\x86\1.33;$(LibraryPath) + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + sha256 + + + $(KMDF_INC_PATH)$(KMDF_VER_PATH);C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\km;%(AdditionalIncludeDirectories) + _AMD64_ + + + + + sha256 + + + $(KMDF_INC_PATH)$(KMDF_VER_PATH);C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\km;%(AdditionalIncludeDirectories) + _AMD64_ + + + + + sha256 + + + + + sha256 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keysas-firewall/usbfilter/usbfilter.vcxproj.filters b/keysas-firewall/usbfilter/usbfilter.vcxproj.filters new file mode 100644 index 0000000..4c9c4a6 --- /dev/null +++ b/keysas-firewall/usbfilter/usbfilter.vcxproj.filters @@ -0,0 +1,31 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + + + Driver Files + + + + + Source Files + + + \ No newline at end of file diff --git a/keysas-firewall/usbfilter/usbfilter.vcxproj.user b/keysas-firewall/usbfilter/usbfilter.vcxproj.user new file mode 100644 index 0000000..fa0bdd3 --- /dev/null +++ b/keysas-firewall/usbfilter/usbfilter.vcxproj.user @@ -0,0 +1,31 @@ + + + + True + None + + + C:\Program Files (x86)\Windows Kits\10\Testing\Tests\Utilities\DefaultDriverPackageInstallationTask.dll + TestVM + TestVM + + Microsoft.DriverKit.DefaultDriverPackageInstallationClass.PerformDefaultDriverPackageInstallation + + FvOn + true + + + True + None + + + C:\Program Files (x86)\Windows Kits\10\Testing\Tests\Utilities\DefaultDriverPackageInstallationTask.dll + TestVM + TestVM + + Microsoft.DriverKit.DefaultDriverPackageInstallationClass.PerformDefaultDriverPackageInstallation + + FvOn + true + + \ No newline at end of file diff --git a/keysas-frontend/package-lock.json b/keysas-frontend/package-lock.json index 7823ac7..8b1c464 100644 --- a/keysas-frontend/package-lock.json +++ b/keysas-frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "keysas-frontend", - "version": "0.1.0", + "version": "2.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "keysas-frontend", - "version": "0.1.0", + "version": "2.1.0", "dependencies": { "@intlify/unplugin-vue-i18n": "^0.8.1", "animate.css": "^4.1.1", diff --git a/keysas-frontend/package.json b/keysas-frontend/package.json index 93d80c7..fb0c6cf 100644 --- a/keysas-frontend/package.json +++ b/keysas-frontend/package.json @@ -1,7 +1,7 @@ { "name": "keysas-frontend", "private": true, - "version": "0.1.0", + "version": "2.1.0", "type": "module", "scripts": { "dev": "vite", diff --git a/keysas-io/Cargo.toml b/keysas-io/Cargo.toml index cb5581f..5ab61b5 100644 --- a/keysas-io/Cargo.toml +++ b/keysas-io/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keysas-io" -version = "2.0.0" +version = "2.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/keysas-sign/Cargo.toml b/keysas-sign/Cargo.toml index 9cc1f65..06e1cb1 100644 --- a/keysas-sign/Cargo.toml +++ b/keysas-sign/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keysas-sign" -version = "1.2.0" +version = "2.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/keysas_lib/Cargo.toml b/keysas_lib/Cargo.toml index 938fe2e..c5cddf6 100644 --- a/keysas_lib/Cargo.toml +++ b/keysas_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keysas_lib" -version = "2.0.0" +version = "2.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html