feat(ci): add test signing step

This commit is contained in:
Federico Terzi 2022-08-28 19:32:50 +02:00
parent 30bb30de8c
commit 2cb9845e26
3 changed files with 56 additions and 26 deletions

View File

@ -8,6 +8,8 @@ on:
branches: branches:
- master - master
- dev - dev
# TODO: remove
- feat/windows-code-sign
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -65,8 +67,24 @@ jobs:
cargo install --force cargo-make --version 0.34.0 cargo install --force cargo-make --version 0.34.0
- name: Test - name: Test
run: cargo make test-binary --profile release run: cargo make test-binary --profile release
- name: Build - name: Build resources
run: cargo make build-windows-all --profile release run: cargo make build-windows-resources --profile release
- name: Sign resources
run: cargo make sign-windows-resources
env:
CODESIGN_PWD: ${{ secrets.WIN_CODESIGN_PWD }}
CODESIGN_CROSS_SIGNED_B64: ${{ secrets.WIN_CODESIGN_INTERMEDIATE_B64 }}
CODESIGN_CERTIFICATE_B64: ${{ secrets.WIN_CODESIGN_CERTIFICATE_B64 }}
- name: Build installer
run: cargo make build-windows-installer --profile release
- name: Sign installer
run: cargo make sign-windows-installer
env:
CODESIGN_PWD: ${{ secrets.WIN_CODESIGN_PWD }}
CODESIGN_CROSS_SIGNED_B64: ${{ secrets.WIN_CODESIGN_INTERMEDIATE_B64 }}
CODESIGN_CERTIFICATE_B64: ${{ secrets.WIN_CODESIGN_CERTIFICATE_B64 }}
- name: Build portable mode archive
run: cargo make build-windows-portable --profile release
- name: Create portable mode archive - name: Create portable mode archive
shell: powershell shell: powershell
run: | run: |

View File

@ -50,6 +50,16 @@ dependencies = ["build-windows-resources"]
[tasks.build-windows-all] [tasks.build-windows-all]
dependencies = ["build-windows-portable", "build-windows-installer"] dependencies = ["build-windows-portable", "build-windows-installer"]
[tasks.sign-windows-resources]
env = { "TARGET_SIGNTOOL_FILE" = "target/windows/resources/espansod.exe" }
script_runner = "@rust"
script = { file = "scripts/sign_windows_exe.rs" }
[tasks.sign-windows-installer]
env = { "TARGET_SIGNTOOL_FILE" = "target/windows/installer/Espanso-Win-Installer-x86_64.exe" }
script_runner = "@rust"
script = { file = "scripts/sign_windows_exe.rs" }
# macOS # macOS
[tasks.build-macos-arm-binary] [tasks.build-macos-arm-binary]

View File

@ -1,12 +1,14 @@
//! ```cargo //! ```cargo
//! [dependencies] //! [dependencies]
//! glob = "0.3.0" //! glob = "0.3.0"
//! envmnt = "*"
//! fs_extra = "1.2.0" //! fs_extra = "1.2.0"
//! base64 = "0.13.0" //! base64 = "0.13.0"
//! anyhow = "1.0.38"
//! ``` //! ```
use anyhow::Result;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command;
const WINDOWS_KITS_LOCATION: &str = "C:/Program Files (x86)/Windows Kits/10/bin"; const WINDOWS_KITS_LOCATION: &str = "C:/Program Files (x86)/Windows Kits/10/bin";
const CERTIFICATE_TARGET_DIR: &str = "target/codesign"; const CERTIFICATE_TARGET_DIR: &str = "target/codesign";
@ -22,7 +24,8 @@ fn main() {
let signtool_path = get_signtool_location().expect("unable to locate signtool exe"); let signtool_path = get_signtool_location().expect("unable to locate signtool exe");
println!("using signtool location: {:?}", signtool_path); println!("using signtool location: {:?}", signtool_path);
let target_exe_file = envmnt::get_or_panic("TARGET_SIGNTOOL_FILE"); let target_exe_file =
std::env::var("TARGET_SIGNTOOL_FILE").expect("TARGET_SIGNTOOL_FILE env variable not found");
let target_exe_path = PathBuf::from(target_exe_file); let target_exe_path = PathBuf::from(target_exe_file);
if !target_exe_path.is_file() { if !target_exe_path.is_file() {
panic!( panic!(
@ -33,17 +36,19 @@ fn main() {
println!("signing file: {:?}", target_exe_path); println!("signing file: {:?}", target_exe_path);
let certificate_pwd = envmnt::get_or_panic("CODESIGN_PWD"); let certificate_pwd = std::env::var("CODESIGN_PWD").expect("CODESIGN_PWD env variable not found");
let cross_signed_certificate_b64 = envmnt::get_or_panic("CODESIGN_CROSS_SIGNED_B64"); let cross_signed_certificate_b64 = std::env::var("CODESIGN_CROSS_SIGNED_B64")
let codesign_certificate_b64 = envmnt::get_or_panic("CODESIGN_CERTIFICATE_B64"); .expect("CODESIGN_CROSS_SIGNED_B64 env variable not found");
let codesign_certificate_b64 = std::env::var("CODESIGN_CERTIFICATE_B64")
.expect("CODESIGN_CERTIFICATE_B64 env variable not found");
let cross_signed_certificate = DecodedCertificate::new( let cross_signed_certificate = DecodedCertificate::new(
cross_signed_certificate_b64, &cross_signed_certificate_b64,
certificate_target_dir.join("SectigoPublicCodeSigningRootR46_AAA.crt"), certificate_target_dir.join("SectigoPublicCodeSigningRootR46_AAA.crt"),
) )
.expect("unable to decode intermediate cross-signed certificate"); .expect("unable to decode intermediate cross-signed certificate");
let codesign_certificate = DecodedCertificate::new( let codesign_certificate = DecodedCertificate::new(
codesign_certificate_b64, &codesign_certificate_b64,
certificate_target_dir.join("codesign.pfx"), certificate_target_dir.join("codesign.pfx"),
) )
.expect("unable to decode codesign certificate"); .expect("unable to decode codesign certificate");
@ -63,7 +68,7 @@ fn main() {
"http://timestamp.sectigo.com/rfc3161", "http://timestamp.sectigo.com/rfc3161",
"/td", "/td",
"sha256", "sha256",
&target_exe_path, &target_exe_path.to_string_lossy(),
]); ]);
let mut handle = cmd.spawn().expect("signtool spawn failed"); let mut handle = cmd.spawn().expect("signtool spawn failed");
@ -77,14 +82,14 @@ fn main() {
fn get_signtool_location() -> Option<PathBuf> { fn get_signtool_location() -> Option<PathBuf> {
let mut path: Option<PathBuf> = None; let mut path: Option<PathBuf> = None;
let mut max_version = 0; let mut max_version = 0;
for entry in glob::glob(format!("{}/*", WINDOWS_KITS_LOCATION)) for entry in glob::glob(&format!("{}/*", WINDOWS_KITS_LOCATION))
.expect("unable to glob windows kits location") .expect("unable to glob windows kits location")
{ {
let entry = entry.expect("unable to unwrap glob entry"); let entry = entry.expect("unable to unwrap glob entry");
if (!entry.is_dir()) { if !entry.is_dir() {
continue; continue;
} }
if (!entry.display().ends_with(".0")) { if !entry.to_string_lossy().ends_with(".0") {
continue; continue;
} }
let folder_name = entry.file_name().expect("unable to extract folder_name"); let folder_name = entry.file_name().expect("unable to extract folder_name");
@ -95,7 +100,7 @@ fn get_signtool_location() -> Option<PathBuf> {
if folder_version > max_version { if folder_version > max_version {
let signtool_path_candidate = entry.join("x86").join("signtool.exe"); let signtool_path_candidate = entry.join("x86").join("signtool.exe");
if signtool_path_candidate.is_file() { if signtool_path_candidate.is_file() {
path = signtool_path_candidate; path = Some(signtool_path_candidate);
max_version = folder_version; max_version = folder_version;
} }
} }
@ -104,26 +109,23 @@ fn get_signtool_location() -> Option<PathBuf> {
return path; return path;
} }
fn decode_b64_and_save_to_file(b64: &str, target_file: &Path) -> Result<()> {
let decoded = base64::decode(b64);
std::fs::write(target_file, decoded)
}
struct DecodedCertificate { struct DecodedCertificate {
decoded_file: PathBuf, decoded_file: PathBuf,
} }
impl DecodedCertificate { impl DecodedCertificate {
pub fn new(b64: &str, target_file: PathBuf) -> Result<()> { pub fn new(b64: &str, target_file: PathBuf) -> Result<Self> {
let decoded = base64::decode(b64); // Keys and certificates are encoded with whitespaces/newlines in them, but we need to remove them
std::fs::write(&target_file, decoded)?; let filtered_b64: String = b64.chars().filter(|c| !c.is_whitespace()).collect();
Self { let decoded = base64::decode(filtered_b64)?;
std::fs::write(&target_file, &decoded)?;
Ok(Self {
decoded_file: target_file, decoded_file: target_file,
} })
} }
pub fn path(&self) -> String { pub fn path(&self) -> String {
self.decoded_file.display() self.decoded_file.to_string_lossy().to_string()
} }
} }
@ -131,6 +133,6 @@ impl DecodedCertificate {
// to minimize the attack surface // to minimize the attack surface
impl Drop for DecodedCertificate { impl Drop for DecodedCertificate {
fn drop(&mut self) { fn drop(&mut self) {
std::fs::remove_file(self.decoded_file).expect("unable to remove certificate file") std::fs::remove_file(&self.decoded_file).expect("unable to remove certificate file")
} }
} }