Skip to content

Commit

Permalink
Support for ZIP notarization fallback
Browse files Browse the repository at this point in the history
Related to #16

This changes the notarization process somewhat:
1. On all platforms, we sign and notarize the app and then package it as a ZIP file, replacing the input ZIP file with its notarized version.
2. On macOS specifically, we then package the notarized app into a DMG, signing and notarizing that as well.
  • Loading branch information
kobaltcore committed Mar 13, 2024
1 parent cb46763 commit 3938c99
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 14 deletions.
4 changes: 3 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ repository = "https://github.com/kobaltcore/renkit"

[dependencies]
# System
zip = "0.6.6"
rsa = "0.9.6"
rand = "0.8.5"
regex = "1.10.2"
Expand Down
57 changes: 56 additions & 1 deletion src/common.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
use crate::renutil::{Instance, Local, Remote};
use std::path::PathBuf;
use anyhow::Result;
use jwalk::{ClientState, DirEntry};
use std::{
fs::File,
io::{Read, Seek, Write},
path::PathBuf,
};
use zip::{write::FileOptions, ZipWriter};

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Version {
Expand Down Expand Up @@ -61,3 +68,51 @@ impl Version {
}
}
}

pub fn zip_dir<T, C>(
it: &mut dyn Iterator<Item = jwalk::Result<DirEntry<C>>>,
prefix: Option<&PathBuf>,
writer: T,
method: zip::CompressionMethod,
) -> Result<()>
where
T: Write + Seek,
C: ClientState,
{
let mut zip = ZipWriter::new(writer);
let options = FileOptions::default()
.compression_method(method)
.unix_permissions(0o755);

let mut buffer = Vec::new();
for entry in it {
let path = entry?.path();
let name = match prefix {
Some(p) => path.strip_prefix(p).unwrap(),
None => path.as_path(),
};

// Write file or directory explicitly
// Some unzip tools unzip files with directory paths correctly, some do not!
if path.is_file() {
// println!("adding file {path:?} as {name:?} ...");
#[allow(deprecated)]
zip.start_file_from_path(name, options)?;
let mut f = File::open(path)?;

f.read_to_end(&mut buffer)?;
zip.write_all(&buffer)?;
buffer.clear();
} else if !name.as_os_str().is_empty() {
// Only if not root! Avoids path spec / warning
// and mapname conversion failed error on unzip
// println!("adding dir {path:?} as {name:?} ...");
#[allow(deprecated)]
zip.add_directory_from_path(name, options)?;
}
}

zip.finish()?;

Ok(())
}
87 changes: 75 additions & 12 deletions src/renotize.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::common::zip_dir;
use anyhow::{anyhow, Result};
use app_store_connect::notary_api::{self, SubmissionResponseStatus};
use apple_codesign::{
Expand All @@ -11,15 +12,27 @@ use apple_codesign::{
};
use indicatif::{ProgressBar, ProgressStyle};
use itertools::Itertools;
use jwalk::WalkDir;
use plist::Value;
use rsa::{pkcs1::EncodeRsaPrivateKey, RsaPrivateKey};
use std::{fs, io::Cursor, path::PathBuf, process::Command, time::Duration};
use std::{
fs::{self, File},
io::Cursor,
path::PathBuf,
process::Command,
time::Duration,
};
use x509_certificate::X509CertificateBuilder;
use zip::CompressionMethod;

const APPLE_TIMESTAMP_URL: &str = "http://timestamp.apple.com/ts01";
const ENTITLEMENTS_PLIST: &str = r#"<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/></dict></plist>"#;

fn notarize_file(input_file: &PathBuf, app_store_key_file: &PathBuf) -> Result<()> {
fn notarize_file(
input_file: &PathBuf,
app_store_key_file: &PathBuf,
staple_file: Option<&PathBuf>,
) -> Result<()> {
let notarizer = Notarizer::from_api_key(&app_store_key_file)?;

println!("Uploading file to notarization service");
Expand Down Expand Up @@ -75,7 +88,11 @@ fn notarize_file(input_file: &PathBuf, app_store_key_file: &PathBuf) -> Result<(

println!("Stapling notarization to file");
let stapler = Stapler::new()?;
stapler.staple_path(&input_file)?;

match staple_file {
Some(sf) => stapler.staple_path(sf)?,
None => stapler.staple_path(input_file)?,
};
}
_ => {
println!("Notarization failed.");
Expand Down Expand Up @@ -182,7 +199,7 @@ pub fn sign_app(input_file: &PathBuf, key_file: &PathBuf, cert_file: &PathBuf) -
}

pub fn notarize_app(input_file: &PathBuf, app_store_key_file: &PathBuf) -> Result<()> {
notarize_file(input_file, app_store_key_file)
notarize_file(input_file, app_store_key_file, None)
}

pub fn pack_dmg(
Expand Down Expand Up @@ -259,7 +276,30 @@ pub fn sign_dmg(input_file: &PathBuf, key_file: &PathBuf, cert_file: &PathBuf) -
}

pub fn notarize_dmg(input_file: &PathBuf, app_store_key_file: &PathBuf) -> Result<()> {
notarize_file(input_file, app_store_key_file)
notarize_file(input_file, app_store_key_file, None)
}

pub fn pack_zip(input_file: &PathBuf, output_file: &PathBuf) -> Result<()> {
let mut files = WalkDir::new(input_file).into_iter();

let file = File::create(output_file).unwrap();

zip_dir(
&mut files,
Some(&input_file.parent().unwrap().to_owned()),
file,
CompressionMethod::Deflated,
)?;

Ok(())
}

pub fn notarize_zip(
input_file: &PathBuf,
app_store_key_file: &PathBuf,
app_file: &PathBuf,
) -> Result<()> {
notarize_file(input_file, app_store_key_file, Some(app_file))
}

pub fn status(uuid: &String, app_store_key_file: &PathBuf) -> Result<()> {
Expand Down Expand Up @@ -322,14 +362,37 @@ pub fn full_run(
sign_app(&app_path, key_file, cert_file)?;
println!("Notarizing app at {:?}", app_path);
notarize_app(&app_path, app_store_key_file)?;
let dmg_path = app_path.with_extension("dmg");
println!("Packing DMG to {:?}", dmg_path);
pack_dmg(&app_path, &dmg_path, &None)?;
println!("Signing DMG at {:?}", dmg_path);
sign_dmg(&dmg_path, key_file, cert_file)?;
println!("Notarizing DMG at {:?}", dmg_path);
notarize_dmg(&dmg_path, app_store_key_file)?;

let zip_path = app_path
.parent()
.unwrap()
.with_file_name(format!(
"{}-notarized",
input_file.file_stem().unwrap().to_string_lossy()
))
.with_extension("zip");
println!("Packing ZIP to {:?}", input_file);
pack_zip(&app_path, &zip_path)?;

fs::remove_file(&input_file)?;
fs::rename(&zip_path, &input_file)?;

if std::env::consts::OS == "macos" {
let dmg_path = input_file.with_extension("dmg");
println!("Packing DMG to {:?}", dmg_path);
pack_dmg(&app_path, &dmg_path, &None)?;
println!("Signing DMG at {:?}", dmg_path);
sign_dmg(&dmg_path, key_file, cert_file)?;
println!("Notarizing DMG at {:?}", dmg_path);
notarize_dmg(&dmg_path, app_store_key_file)?;

fs::remove_dir_all(app_path.parent().unwrap())?;
} else {
println!("Skipping DMG creation and signing: Only supported on macOS.");
}

println!("Done!");

Ok(())
}

Expand Down

0 comments on commit 3938c99

Please sign in to comment.