Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion crates/cargo-wdk/src/actions/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use wdk_build::{
metadata::{TryFromCargoMetadataError, Wdk},
};

use crate::actions::Profile;
use crate::actions::{Profile, SignMode};
#[double]
use crate::providers::{exec::CommandExec, fs::Fs, metadata::Metadata, wdk_build::WdkBuild};

Expand All @@ -38,6 +38,7 @@ pub struct BuildActionParams<'a> {
pub profile: Option<&'a Profile>,
pub target_arch: Option<CpuArchitecture>,
pub verify_signature: bool,
pub sign_mode: SignMode,
pub is_sample_class: bool,
pub verbosity_level: clap_verbosity_flag::Verbosity,
}
Expand All @@ -49,6 +50,7 @@ pub struct BuildAction<'a> {
profile: Option<&'a Profile>,
target_arch: Option<CpuArchitecture>,
verify_signature: bool,
sign_mode: SignMode,
is_sample_class: bool,
verbosity_level: clap_verbosity_flag::Verbosity,

Expand Down Expand Up @@ -90,6 +92,7 @@ impl<'a> BuildAction<'a> {
profile: params.profile,
target_arch: params.target_arch,
verify_signature: params.verify_signature,
sign_mode: params.sign_mode,
is_sample_class: params.is_sample_class,
verbosity_level: params.verbosity_level,
wdk_build,
Expand Down Expand Up @@ -386,6 +389,7 @@ impl<'a> BuildAction<'a> {
target_dir: &target_dir,
target_arch: &target_arch,
verify_signature: self.verify_signature,
sign_mode: self.sign_mode,
sample_class: self.is_sample_class,
driver_model,
},
Expand Down
54 changes: 38 additions & 16 deletions crates/cargo-wdk/src/actions/build/package_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ use windows::{

#[double]
use crate::providers::{exec::CommandExec, fs::Fs, wdk_build::WdkBuild};
use crate::{actions::build::error::PackageTaskError, providers::error::FileError};
use crate::{
actions::{SignMode, build::error::PackageTaskError},
providers::error::FileError,
};

// FIXME: This range is inclusive of 25798. Update with range end after /sample
// flag is added to InfVerif CLI
Expand All @@ -44,6 +47,7 @@ pub struct PackageTaskParams<'a> {
pub target_dir: &'a Path,
pub target_arch: &'a CpuArchitecture,
pub verify_signature: bool,
pub sign_mode: SignMode,
pub sample_class: bool,
pub driver_model: DriverConfig,
}
Expand All @@ -52,6 +56,7 @@ pub struct PackageTaskParams<'a> {
pub struct PackageTask<'a> {
package_name: String,
verify_signature: bool,
sign_mode: SignMode,
sample_class: bool,

// src paths
Expand Down Expand Up @@ -162,6 +167,7 @@ impl<'a> PackageTask<'a> {
Self {
package_name,
verify_signature: params.verify_signature,
sign_mode: params.sign_mode,
sample_class: params.sample_class,
src_inx_file_path,
src_driver_binary_file_path,
Expand Down Expand Up @@ -236,24 +242,36 @@ impl<'a> PackageTask<'a> {
self.copy(&self.src_map_file_path, &self.dest_map_file_path)?;
self.run_stampinf()?;
self.run_inf2cat()?;
self.generate_certificate()?;
self.copy(&self.src_cert_file_path, &self.dest_cert_file_path)?;
self.run_signtool_sign(
&self.dest_driver_binary_path,
WDR_TEST_CERT_STORE,
WDR_LOCAL_TEST_CERT,
)?;
self.run_signtool_sign(
&self.dest_cat_file_path,
WDR_TEST_CERT_STORE,
WDR_LOCAL_TEST_CERT,
)?;
match self.sign_mode {
SignMode::Test => {
self.generate_certificate()?;
self.copy(&self.src_cert_file_path, &self.dest_cert_file_path)?;
self.run_signtool_sign(
&self.dest_driver_binary_path,
WDR_TEST_CERT_STORE,
WDR_LOCAL_TEST_CERT,
)?;
self.run_signtool_sign(
&self.dest_cat_file_path,
WDR_TEST_CERT_STORE,
WDR_LOCAL_TEST_CERT,
)?;
}
SignMode::Off => {
info!("Sign mode is 'Off'; skipping certificate generation and signing");
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When --sign-mode=off, the package directory is not cleaned (it’s only created if missing). If a previous build ran in Test mode, WDRLocalTestCert.cer may already exist in the package folder and will be left behind because the copy step is skipped, which can defeat the goal of producing a package without the test cert artifact. Consider explicitly removing any existing cert file(s) (both in the package folder and staged under target) when SignMode::Off is selected, or recreate/clean the package directory before copying artifacts (may require adding a remove_file/remove_dir_all method to the Fs provider so this is testable).

Suggested change
info!("Sign mode is 'Off'; skipping certificate generation and signing");
info!("Sign mode is 'Off'; skipping certificate generation and signing");
for cert_path in [&self.dest_cert_file_path, &self.src_cert_file_path] {
match std::fs::remove_file(cert_path) {
Ok(()) => {
info!(
"Removed stale certificate artifact: {}",
cert_path.to_string_lossy()
);
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
Err(err) => return Err(err.into()),
}
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked how MSBuild/WDK handles this in WindowsDriver.Common.targets (10.0.26100.0): all signing targets are gated on SignMode == TestSign|ProductionSign, same as this PR. There's no mode-switch cleanup. I think we should let cleanup be handled by cargo wdk clean similar to MSBuild's Clean. If this UX becomes an issue, I can revisit it and implement something to ensure the final package is not stale.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we adding the cert to the package? Is that something Visual Studio does too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! cargo-wdk actually follows the cargo-make based approach in rust-driver-makefile.toml:452-468, which in turn was derived from WDK's MSBuild targets. Shipping the cert for "Test-Signing" workflows makes sense because the cert needs to be installed on the target (test) machine. I need to re-verify the behavior for C++ drivers in Visual Studio.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay.

Yeah, let's verify the C++ behaviour.

Copy link
Copy Markdown
Contributor

@gurry gurry May 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
info!("Sign mode is 'Off'; skipping certificate generation and signing");
info!("Sign mode is 'off'; skipping certificate generation and signing");

We want to use and promote lower case values of sign mode everywhere since they are easier to type.

}
}
self.run_infverif()?;
// Verify signatures only when --verify-signature flag = true is passed
// and signing was done (sign mode is not 'Off').
if self.verify_signature {
info!("Verifying signatures for driver binary and cat file using signtool");
self.run_signtool_verify(&self.dest_driver_binary_path)?;
self.run_signtool_verify(&self.dest_cat_file_path)?;
if matches!(self.sign_mode, SignMode::Off) {
warn!("Skipping signature verification because sign mode is 'Off'");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
warn!("Skipping signature verification because sign mode is 'Off'");
warn!("Skipping signature verification because sign mode is 'off'");

} else {
info!("Verifying signatures for driver binary and cat file using signtool");
self.run_signtool_verify(&self.dest_driver_binary_path)?;
self.run_signtool_verify(&self.dest_cat_file_path)?;
}
}
Ok(())
}
Expand Down Expand Up @@ -636,6 +654,7 @@ mod tests {
driver_model: DriverConfig::Kmdf(KmdfConfig::default()),
sample_class: false,
verify_signature: false,
sign_mode: SignMode::Test,
};
let dest_root = target_dir.join(format!("{package_name}_package"));

Expand Down Expand Up @@ -699,6 +718,7 @@ mod tests {
driver_model: DriverConfig::Kmdf(KmdfConfig::default()),
sample_class: false,
verify_signature: false,
sign_mode: SignMode::Test,
};

let command_exec = CommandExec::default();
Expand All @@ -725,6 +745,7 @@ mod tests {
driver_model: DriverConfig::Kmdf(KmdfConfig::default()),
sample_class: false,
verify_signature: false,
sign_mode: SignMode::Test,
};

let command_exec = CommandExec::default();
Expand Down Expand Up @@ -760,6 +781,7 @@ mod tests {
driver_model: DriverConfig::Kmdf(KmdfConfig::default()),
sample_class: false,
verify_signature: false,
sign_mode: SignMode::Test,
};

let wdk_build = WdkBuild::default();
Expand Down
157 changes: 157 additions & 0 deletions crates/cargo-wdk/src/actions/build/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use crate::providers::{
use crate::{
actions::{
Profile,
SignMode,
build::{BuildAction, BuildActionParams, error::BuildActionError},
to_target_triple,
},
Expand Down Expand Up @@ -265,6 +266,132 @@ pub fn given_a_driver_project_when_verify_signature_is_true_then_it_builds_succe
);
}

// Given: A driver project
// When: --sign-mode=off is provided
// Then: It builds successfully and skips all certificate generation and
// signing steps (no makecert / certmgr / signtool sign calls).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove these comments for all the new tests for consistency because we don't use them on existing tests either. The function names themselves are super verbose so no need for comments.

#[test]
pub fn given_a_driver_project_when_sign_mode_is_off_then_signing_and_verification_steps_are_skipped()
{
// Input CLI args
let cwd = PathBuf::from("C:\\tmp");
let profile = None;
let target_arch = CpuArchitecture::Amd64;
let verify_signature = false;
let sample_class = false;

// Driver project data
let driver_type = "KMDF";
let driver_name = "sample-kmdf";
let driver_version = "0.0.1";
let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33);
let (workspace_member, package) =
get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata));

let cargo_build_output =
create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile);
let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class)
.with_sign_mode(SignMode::Off)
.set_up_standalone_driver_project((workspace_member, package))
.expect_default_build_task_steps(driver_name, Some(cargo_build_output))
.expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None)
.expect_package_task_steps_with_sign_mode_off(driver_name, driver_type, target_arch);

assert_build_action_run_with_env_is_success(
&cwd,
profile,
None,
verify_signature,
sample_class,
test_build_action,
);
}

// Given: A sample-class driver project
// When: --sign-mode=off is provided alongside --sample
// Then: It builds successfully, signing is skipped, and infverif still runs
// with the /msft sample-class flag (i.e., sample-class processing is
// independent of signing).
#[test]
pub fn given_a_sample_class_driver_project_when_sign_mode_is_off_then_signing_is_skipped_and_sample_infverif_still_runs()
{
// Input CLI args
let cwd = PathBuf::from("C:\\tmp");
let profile = None;
let target_arch = CpuArchitecture::Amd64;
let verify_signature = false;
let sample_class = true;

// Driver project data
let driver_type = "KMDF";
let driver_name = "sample-kmdf";
let driver_version = "0.0.1";
let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33);
let (workspace_member, package) =
get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata));

let cargo_build_output =
create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile);
let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class)
.with_sign_mode(SignMode::Off)
.set_up_standalone_driver_project((workspace_member, package))
.expect_default_build_task_steps(driver_name, Some(cargo_build_output))
.expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None)
.expect_package_task_steps_with_sign_mode_off(driver_name, driver_type, target_arch)
.expect_detect_wdk_build_number(25100u32);

assert_build_action_run_with_env_is_success(
&cwd,
profile,
None,
verify_signature,
sample_class,
test_build_action,
);
}

// Given: A driver project
// When: --sign-mode=off and --verify-signature=true are both provided
// Then: Defensive guard in PackageTask::run() takes effect — signtool verify
// is skipped (the CLI normally rejects this combo, but the package task
// itself must not invoke verification when there is nothing signed).
#[test]
pub fn given_a_driver_project_when_sign_mode_is_off_and_verify_signature_is_true_then_verification_is_skipped()
{
// Input CLI args
let cwd = PathBuf::from("C:\\tmp");
let profile = None;
let target_arch = CpuArchitecture::Amd64;
let verify_signature = true;
let sample_class = false;

// Driver project data
let driver_type = "KMDF";
let driver_name = "sample-kmdf";
let driver_version = "0.0.1";
let wdk_metadata = get_cargo_metadata_wdk_metadata(driver_type, 1, 33);
let (workspace_member, package) =
get_cargo_metadata_package(&cwd, driver_name, driver_version, Some(&wdk_metadata));

let cargo_build_output =
create_cargo_build_output_json(driver_name, driver_version, &cwd, None, profile);
let test_build_action = &TestBuildAction::new(cwd.clone(), profile, None, sample_class)
.with_sign_mode(SignMode::Off)
.set_up_standalone_driver_project((workspace_member, package))
.expect_default_build_task_steps(driver_name, Some(cargo_build_output))
.expect_probe_target_arch_using_cargo_rustc(&cwd, target_arch, None)
.expect_package_task_steps_with_sign_mode_off(driver_name, driver_type, target_arch);

assert_build_action_run_with_env_is_success(
&cwd,
profile,
None,
verify_signature,
sample_class,
test_build_action,
);
}

#[test]
pub fn given_a_driver_project_when_self_signed_exists_then_it_should_skip_calling_makecert() {
// Input CLI args
Expand Down Expand Up @@ -1600,6 +1727,7 @@ fn initialize_build_action<'a>(
profile,
target_arch,
verify_signature,
sign_mode: test_build_action.sign_mode,
is_sample_class: sample_class,
verbosity_level: clap_verbosity_flag::Verbosity::new(1, 0),
},
Expand Down Expand Up @@ -1662,6 +1790,7 @@ struct TestBuildAction {
profile: Option<Profile>,
target_arch: Option<CpuArchitecture>,
sample_class: bool,
sign_mode: SignMode,

cargo_metadata: Option<CargoMetadata>,
// mocks
Expand All @@ -1688,6 +1817,7 @@ impl TestBuildAction {
profile,
target_arch,
sample_class,
sign_mode: SignMode::Test,
mock_run_command,
mock_wdk_build_provider,
mock_fs_provider,
Expand All @@ -1696,6 +1826,11 @@ impl TestBuildAction {
}
}

fn with_sign_mode(mut self, sign_mode: SignMode) -> Self {
self.sign_mode = sign_mode;
self
}

fn set_up_standalone_driver_project(
mut self,
package_metadata: (TestMetadataWorkspaceMemberId, TestMetadataPackage),
Expand Down Expand Up @@ -1826,6 +1961,28 @@ impl TestBuildAction {
.expect_signtool_verify_cat_file(driver_name, &cwd, None)
}

/// Sets up package-task expectations for `SignMode::Off`: stampinf,
/// inf2cat, and infverif are still expected, but all certificate
/// generation, signing, and signature-verification steps are skipped.
fn expect_package_task_steps_with_sign_mode_off(
self,
driver_name: &str,
driver_type: &str,
target_arch: CpuArchitecture,
) -> Self {
let cwd = self.cwd.clone();
self.expect_final_package_dir_exists(driver_name, &cwd, true)
.expect_inx_file_exists(driver_name, &cwd, true)
.expect_rename_driver_binary_dll_to_sys(driver_name, &cwd)
.expect_copy_driver_binary_sys_to_package_folder(driver_name, &cwd, true)
.expect_copy_pdb_file_to_package_folder(driver_name, &cwd, true)
.expect_copy_inx_file_to_package_folder(driver_name, &cwd, true, &cwd)
.expect_copy_map_file_to_package_folder(driver_name, &cwd, true)
.expect_stampinf(driver_name, &cwd, target_arch, None)
.expect_inf2cat(driver_name, &cwd, target_arch, None)
.expect_infverif(driver_name, &cwd, driver_type, None)
}

fn expect_default_package_task_steps_for_workspace(
self,
driver_name: &str,
Expand Down
Loading
Loading