use std::fs::File;
use std::io;
use std::marker::PhantomData;
use std::mem;
use std::path::{Path, PathBuf};
use crate::credential::Credential;
use crate::digest::{Digest, JpLevel, LeafHash};
use crate::error::{Error, Result};
use crate::native::{NativeJob, require_readable};
use crate::policy::{Network, Timestamp, TrustAnchors};
use crate::sys::cmd_type_t;
use crate::{Signed, Unsigned};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct NeedsOutput;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Ready;
macro_rules! retarget {
($value:expr) => {{
unsafe { mem::transmute($value) }
}};
}
macro_rules! with_output {
($($Ty:ident),+ $(,)?) => {
$(
impl $Ty<NeedsOutput> {
pub fn output(self, output: impl AsRef<Path>) -> $Ty<Ready> {
let mut next: $Ty<Ready> = retarget!(self);
next.job.output = Some(output.as_ref().to_path_buf());
next
}
}
)+
};
}
macro_rules! setter {
($(#[$meta:meta])* $name:ident($arg:ident: $Ty:ty) => $($path:ident).+) => {
$(#[$meta])*
pub fn $name(mut self, $arg: $Ty) -> Self {
self.$($path).+ = $arg;
self
}
};
}
macro_rules! setter_some {
($(#[$meta:meta])* $name:ident($arg:ident: $Ty:ty) => $($path:ident).+) => {
$(#[$meta])*
pub fn $name(mut self, $arg: $Ty) -> Self {
self.$($path).+ = Some($arg);
self
}
};
}
macro_rules! setter_path {
($(#[$meta:meta])* $name:ident($arg:ident) => $($path:ident).+) => {
$(#[$meta])*
pub fn $name(mut self, $arg: impl AsRef<Path>) -> Self {
self.$($path).+ = Some($arg.as_ref().to_path_buf());
self
}
};
}
macro_rules! setter_string {
($(#[$meta:meta])* $name:ident($arg:ident) => $($path:ident).+) => {
$(#[$meta])*
pub fn $name(mut self, $arg: impl Into<String>) -> Self {
self.$($path).+ = Some($arg.into());
self
}
};
}
macro_rules! flag {
($(#[$meta:meta])* $name:ident => $($path:ident).+) => {
$(#[$meta])*
pub fn $name(mut self) -> Self {
self.$($path).+ = true;
self
}
};
}
macro_rules! push {
($(#[$meta:meta])* $name:ident($arg:ident: $Ty:ty) => $($path:ident).+) => {
$(#[$meta])*
pub fn $name(mut self, $arg: $Ty) -> Self {
self.$($path).+.push($arg);
self
}
};
}
#[derive(Clone, Debug)]
#[must_use = "jobs do nothing until you call the command verb"]
pub struct Sign<State> {
job: NativeJob,
_state: PhantomData<State>,
}
with_output!(Sign);
impl Sign<Ready> {
pub fn new(input: impl AsRef<Path>, output: impl AsRef<Path>, credential: Credential) -> Self {
sign_from_unsigned(input.as_ref().to_path_buf(), credential).output(output)
}
pub fn sign(self) -> Result<Signed> {
let output = self.job.output.clone().expect("Ready always has output");
self.job.run("sign")?;
Ok(Signed::from_path_unchecked(output))
}
}
impl<State> Sign<State> {
setter!(digest(digest: Digest) => job.digest);
push!(timestamp(timestamp: Timestamp) => job.timestamps);
setter!(network(network: Network) => job.network);
setter!(options(options: crate::format::AuthenticodeOptions) => job.options);
setter_path!(
additional_certs(path) => job.additional_certs
);
setter_some!(
jp(level: JpLevel) => job.options.jp
);
setter_string!(description(description) => job.options.description);
setter_string!(url(url) => job.options.url);
flag!(nest => job.options.nest);
flag!(page_hashes => job.options.page_hashes);
flag!(commercial => job.options.commercial);
flag!(pem => job.options.pem);
flag!(verbose => job.verbose);
flag!(no_legacy => job.no_legacy);
setter_some!(time(unix_time: i64) => job.time);
}
pub(crate) fn sign_from_unsigned(input: PathBuf, credential: Credential) -> Sign<NeedsOutput> {
let mut job = NativeJob::new(cmd_type_t::CMD_SIGN, input);
job.credential = Some(credential);
Sign {
job,
_state: PhantomData,
}
}
#[derive(Clone, Debug)]
#[must_use = "jobs do nothing until you call check()"]
pub struct Verify {
job: NativeJob,
}
impl Verify {
pub fn new(input: impl AsRef<Path>) -> Self {
Self {
job: NativeJob::new(cmd_type_t::CMD_VERIFY, input.as_ref().to_path_buf()),
}
}
setter_path!(catalog(path) => job.catalog);
setter!(network(network: Network) => job.network);
flag!(ignore_timestamp => job.ignore_timestamp);
flag!(ignore_cdp => job.ignore_cdp);
flag!(ignore_crl => job.ignore_crl);
setter_some!(time(unix_time: i64) => job.time);
flag!(verbose => job.verbose);
pub fn trust(mut self, trust: TrustAnchors) -> Self {
self.job.ca_file = trust.ca_file;
self.job.crl_file = trust.crl_file;
self.job.tsa_ca = trust.tsa_ca;
self.job.tsa_crl = trust.tsa_crl;
self
}
pub fn index(mut self, index: u32) -> Self {
self.job.index = Some(index as i32);
self
}
pub fn require_leaf_hash(mut self, hash: LeafHash<'_>) -> Self {
self.job.leafhash = Some(hash.to_string());
self
}
pub fn check(self) -> Result<()> {
require_readable(&self.job.input, "input")?;
self.job.run("verify")
}
}
#[derive(Clone, Debug)]
#[must_use = "jobs do nothing until you call add()"]
pub struct Add<State> {
job: NativeJob,
_state: PhantomData<State>,
}
with_output!(Add);
impl Add<Ready> {
pub fn new(input: impl AsRef<Path>, output: impl AsRef<Path>) -> Self {
add_from_signed(input.as_ref().to_path_buf()).output(output)
}
pub fn add(self) -> Result<Signed> {
let output = self.job.output.clone().expect("Ready always has output");
self.job.run("add")?;
Ok(Signed::from_path_unchecked(output))
}
}
impl<State> Add<State> {
push!(timestamp(timestamp: Timestamp) => job.timestamps);
setter!(network(network: Network) => job.network);
setter!(digest(digest: Digest) => job.digest);
setter_path!(unauthenticated_blob(path) => job.options.blob);
flag!(msi_dse => job.options.msi_dse);
flag!(verbose => job.verbose);
pub fn index(mut self, index: u32) -> Self {
self.job.index = Some(index as i32);
self
}
}
pub(crate) fn add_from_signed(input: PathBuf) -> Add<NeedsOutput> {
Add {
job: NativeJob::new(cmd_type_t::CMD_ADD, input),
_state: PhantomData,
}
}
#[derive(Clone, Debug)]
#[must_use = "call reader() to extract"]
pub struct ExtractData {
job: NativeJob,
}
impl ExtractData {
setter!(digest(digest: Digest) => job.digest);
flag!(pem => job.options.pem);
flag!(page_hashes => job.options.page_hashes);
pub fn reader(mut self) -> Result<Extracted> {
Extracted::run("osslsigncode-data", move |output| {
self.job.output = Some(output);
self.job.run("extract-data")
})
}
}
pub(crate) fn extract_data_from(input: PathBuf) -> ExtractData {
ExtractData {
job: NativeJob::new(cmd_type_t::CMD_EXTRACT_DATA, input),
}
}
#[derive(Clone, Debug)]
#[must_use = "call reader() to extract"]
pub struct ExtractSignature {
job: NativeJob,
}
impl ExtractSignature {
flag!(pem => job.options.pem);
pub fn reader(mut self) -> Result<Extracted> {
Extracted::run("osslsigncode-sig", move |output| {
self.job.output = Some(output);
self.job.run("extract-signature")
})
}
}
pub(crate) fn extract_signature_from(input: PathBuf) -> ExtractSignature {
ExtractSignature {
job: NativeJob::new(cmd_type_t::CMD_EXTRACT, input),
}
}
#[derive(Clone, Debug)]
#[must_use = "jobs do nothing until you call attach()"]
pub struct AttachSignature<State> {
job: NativeJob,
_state: PhantomData<State>,
}
with_output!(AttachSignature);
impl AttachSignature<Ready> {
pub fn new(
input: impl AsRef<Path>,
output: impl AsRef<Path>,
signature: impl AsRef<Path>,
) -> Self {
attach_from(
input.as_ref().to_path_buf(),
signature.as_ref().to_path_buf(),
)
.output(output)
}
pub fn attach(self) -> Result<Signed> {
let output = self.job.output.clone().expect("Ready always has output");
self.job.run("attach-signature")?;
Ok(Signed::from_path_unchecked(output))
}
}
impl<State> AttachSignature<State> {
setter!(digest(digest: Digest) => job.digest);
flag!(nest => job.options.nest);
}
pub(crate) fn attach_from(input: PathBuf, signature: PathBuf) -> AttachSignature<NeedsOutput> {
let mut job = NativeJob::new(cmd_type_t::CMD_ATTACH, input);
job.signature = Some(signature);
AttachSignature {
job,
_state: PhantomData,
}
}
#[derive(Clone, Debug)]
#[must_use = "jobs do nothing until you call strip()"]
pub struct RemoveSignature<State> {
job: NativeJob,
_state: PhantomData<State>,
}
with_output!(RemoveSignature);
impl RemoveSignature<Ready> {
pub fn new(input: impl AsRef<Path>, output: impl AsRef<Path>) -> Self {
remove_from(input.as_ref().to_path_buf()).output(output)
}
pub fn strip(self) -> Result<Unsigned> {
let output = self.job.output.clone().expect("Ready always has output");
self.job.run("remove-signature")?;
Ok(Unsigned::from_path_unchecked(output))
}
}
pub(crate) fn remove_from(input: PathBuf) -> RemoveSignature<NeedsOutput> {
RemoveSignature {
job: NativeJob::new(cmd_type_t::CMD_REMOVE, input),
_state: PhantomData,
}
}
pub struct Extracted {
file: File,
_dir: tempfile::TempDir,
}
impl Extracted {
fn run(prefix: &str, run: impl FnOnce(PathBuf) -> Result<()>) -> Result<Self> {
let dir = tempfile::Builder::new()
.prefix(prefix)
.tempdir()
.map_err(|source| Error::Runtime {
message: format!("failed to create temp dir: {source}"),
})?;
let path = dir.path().join("out");
run(path.clone())?;
let file = File::open(&path).map_err(|source| crate::error::io("output", &path, source))?;
Ok(Self { file, _dir: dir })
}
}
impl io::Read for Extracted {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
io::Read::read(&mut self.file, buf)
}
}