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