feat(ci): first draft of signtool script
This commit is contained in:
		
							parent
							
								
									9154b7af85
								
							
						
					
					
						commit
						30bb30de8c
					
				
							
								
								
									
										136
									
								
								scripts/sign_windows_exe.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								scripts/sign_windows_exe.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,136 @@
 | 
			
		|||
//! ```cargo
 | 
			
		||||
//! [dependencies]
 | 
			
		||||
//! glob = "0.3.0"
 | 
			
		||||
//! envmnt = "*"
 | 
			
		||||
//! fs_extra = "1.2.0"
 | 
			
		||||
//! base64 = "0.13.0"
 | 
			
		||||
//! ```
 | 
			
		||||
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
const WINDOWS_KITS_LOCATION: &str = "C:/Program Files (x86)/Windows Kits/10/bin";
 | 
			
		||||
const CERTIFICATE_TARGET_DIR: &str = "target/codesign";
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
  let _ = std::fs::remove_dir_all(CERTIFICATE_TARGET_DIR);
 | 
			
		||||
  std::fs::create_dir_all(CERTIFICATE_TARGET_DIR).expect("unable to create target directory");
 | 
			
		||||
  let certificate_target_dir = PathBuf::from(CERTIFICATE_TARGET_DIR);
 | 
			
		||||
  if !certificate_target_dir.is_dir() {
 | 
			
		||||
    panic!("expected target directory, found none");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let signtool_path = get_signtool_location().expect("unable to locate signtool exe");
 | 
			
		||||
  println!("using signtool location: {:?}", signtool_path);
 | 
			
		||||
 | 
			
		||||
  let target_exe_file = envmnt::get_or_panic("TARGET_SIGNTOOL_FILE");
 | 
			
		||||
  let target_exe_path = PathBuf::from(target_exe_file);
 | 
			
		||||
  if !target_exe_path.is_file() {
 | 
			
		||||
    panic!(
 | 
			
		||||
      "target file '{}' cannot be found",
 | 
			
		||||
      target_exe_path.display()
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  println!("signing file: {:?}", target_exe_path);
 | 
			
		||||
 | 
			
		||||
  let certificate_pwd = envmnt::get_or_panic("CODESIGN_PWD");
 | 
			
		||||
  let cross_signed_certificate_b64 = envmnt::get_or_panic("CODESIGN_CROSS_SIGNED_B64");
 | 
			
		||||
  let codesign_certificate_b64 = envmnt::get_or_panic("CODESIGN_CERTIFICATE_B64");
 | 
			
		||||
 | 
			
		||||
  let cross_signed_certificate = DecodedCertificate::new(
 | 
			
		||||
    cross_signed_certificate_b64,
 | 
			
		||||
    certificate_target_dir.join("SectigoPublicCodeSigningRootR46_AAA.crt"),
 | 
			
		||||
  )
 | 
			
		||||
  .expect("unable to decode intermediate cross-signed certificate");
 | 
			
		||||
  let codesign_certificate = DecodedCertificate::new(
 | 
			
		||||
    codesign_certificate_b64,
 | 
			
		||||
    certificate_target_dir.join("codesign.pfx"),
 | 
			
		||||
  )
 | 
			
		||||
  .expect("unable to decode codesign certificate");
 | 
			
		||||
 | 
			
		||||
  let mut cmd = Command::new(signtool_path);
 | 
			
		||||
  cmd.args(&[
 | 
			
		||||
    "sign",
 | 
			
		||||
    "/fd",
 | 
			
		||||
    "SHA256",
 | 
			
		||||
    "/p",
 | 
			
		||||
    &certificate_pwd,
 | 
			
		||||
    "/ac",
 | 
			
		||||
    &cross_signed_certificate.path(),
 | 
			
		||||
    "/f",
 | 
			
		||||
    &codesign_certificate.path(),
 | 
			
		||||
    "/tr",
 | 
			
		||||
    "http://timestamp.sectigo.com/rfc3161",
 | 
			
		||||
    "/td",
 | 
			
		||||
    "sha256",
 | 
			
		||||
    &target_exe_path,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  let mut handle = cmd.spawn().expect("signtool spawn failed");
 | 
			
		||||
  let result = handle.wait().expect("unable to read signtool exit status");
 | 
			
		||||
  if !result.success() {
 | 
			
		||||
    panic!("signtool failed");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Inspired by: https://github.com/dlemstra/code-sign-action/blob/main/index.ts#L143
 | 
			
		||||
fn get_signtool_location() -> Option<PathBuf> {
 | 
			
		||||
  let mut path: Option<PathBuf> = None;
 | 
			
		||||
  let mut max_version = 0;
 | 
			
		||||
  for entry in glob::glob(format!("{}/*", WINDOWS_KITS_LOCATION))
 | 
			
		||||
    .expect("unable to glob windows kits location")
 | 
			
		||||
  {
 | 
			
		||||
    let entry = entry.expect("unable to unwrap glob entry");
 | 
			
		||||
    if (!entry.is_dir()) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (!entry.display().ends_with(".0")) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    let folder_name = entry.file_name().expect("unable to extract folder_name");
 | 
			
		||||
    let folder_version_str = folder_name.to_string_lossy().replace(".", "");
 | 
			
		||||
    let folder_version = folder_version_str
 | 
			
		||||
      .parse::<i32>()
 | 
			
		||||
      .expect("invalid folder version string");
 | 
			
		||||
    if folder_version > max_version {
 | 
			
		||||
      let signtool_path_candidate = entry.join("x86").join("signtool.exe");
 | 
			
		||||
      if signtool_path_candidate.is_file() {
 | 
			
		||||
        path = signtool_path_candidate;
 | 
			
		||||
        max_version = folder_version;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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 {
 | 
			
		||||
  decoded_file: PathBuf,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DecodedCertificate {
 | 
			
		||||
  pub fn new(b64: &str, target_file: PathBuf) -> Result<()> {
 | 
			
		||||
    let decoded = base64::decode(b64);
 | 
			
		||||
    std::fs::write(&target_file, decoded)?;
 | 
			
		||||
    Self {
 | 
			
		||||
      decoded_file: target_file,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn path(&self) -> String {
 | 
			
		||||
    self.decoded_file.display()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete the certificate files when they are not needed anymore
 | 
			
		||||
// to minimize the attack surface
 | 
			
		||||
impl Drop for DecodedCertificate {
 | 
			
		||||
  fn drop(&mut self) {
 | 
			
		||||
    std::fs::remove_file(self.decoded_file).expect("unable to remove certificate file")
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user