mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-09-11 16:08:21 +03:00
feat(cli): iOS signing for CI usage (#9963)
* feat(cli): iOS signing for CI usage * license headers * change file * chore: support more cert types * xplicit method arg * keep keychain alive * fix early keychano drop * set team id * use common name as cert name
This commit is contained in:
parent
532b3b1c03
commit
7c7fa0964d
@ -188,10 +188,14 @@
|
||||
"path": "./core/tauri-utils",
|
||||
"manager": "rust"
|
||||
},
|
||||
"tauri-macos-sign": {
|
||||
"path": "./tooling/macos-sign",
|
||||
"manager": "rust"
|
||||
},
|
||||
"tauri-bundler": {
|
||||
"path": "./tooling/bundler",
|
||||
"manager": "rust",
|
||||
"dependencies": ["tauri-utils"]
|
||||
"dependencies": ["tauri-utils", "tauri-macos-sign"]
|
||||
},
|
||||
"tauri-runtime": {
|
||||
"path": "./core/tauri-runtime",
|
||||
@ -262,7 +266,7 @@
|
||||
"tauri-cli": {
|
||||
"path": "./tooling/cli",
|
||||
"manager": "rust",
|
||||
"dependencies": ["tauri-bundler", "tauri-utils"],
|
||||
"dependencies": ["tauri-bundler", "tauri-utils", "tauri-macos-sign"],
|
||||
"postversion": [
|
||||
"cargo check",
|
||||
"cargo build --manifest-path ../../core/tauri-config-schema/Cargo.toml"
|
||||
|
6
.changes/ios-export-method.md
Normal file
6
.changes/ios-export-method.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-cli": patch:feat
|
||||
"@tauri-apps/cli": patch:feat
|
||||
---
|
||||
|
||||
Added `--method` argument for `ios build` to select the export options' method.
|
6
.changes/ios-signing.md
Normal file
6
.changes/ios-signing.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-cli": patch:feat
|
||||
"@tauri-apps/cli": patch:feat
|
||||
---
|
||||
|
||||
Setup iOS signing by reading `IOS_CERTIFICATE`, `IOS_CERTIFICATE_PASSWORD` and `IOS_MOBILE_PROVISION` environment variables.
|
5
.changes/macos-sign-initial-release.md
Normal file
5
.changes/macos-sign-initial-release.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-macos-sign": minor:feat
|
||||
---
|
||||
|
||||
Initial release.
|
@ -58,6 +58,7 @@ glob = "0.3"
|
||||
icns = { package = "tauri-icns", version = "0.1" }
|
||||
time = { version = "0.3", features = [ "formatting" ] }
|
||||
plist = "1"
|
||||
tauri-macos-sign = { version = "0.0.0", path = "../macos-sign" }
|
||||
|
||||
[target."cfg(any(target_os = \"macos\", target_os = \"windows\"))".dependencies]
|
||||
regex = "1"
|
||||
|
@ -118,12 +118,12 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
remove_extra_attr(&app_bundle_path)?;
|
||||
|
||||
// sign application
|
||||
sign(sign_paths, identity, settings)?;
|
||||
let keychain = sign(sign_paths, identity, settings)?;
|
||||
|
||||
// notarization is required for distribution
|
||||
match notarize_auth() {
|
||||
Ok(auth) => {
|
||||
notarize(app_bundle_path.clone(), auth, settings)?;
|
||||
notarize(&keychain, app_bundle_path.clone(), &auth)?;
|
||||
}
|
||||
Err(e) => {
|
||||
if matches!(e, NotarizeAuthError::MissingTeamId) {
|
||||
|
@ -6,390 +6,53 @@
|
||||
use std::{
|
||||
env::{var, var_os},
|
||||
ffi::OsString,
|
||||
fs::File,
|
||||
io::prelude::*,
|
||||
path::PathBuf,
|
||||
process::Command,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::{bundle::common::CommandExt, Settings};
|
||||
use anyhow::Context;
|
||||
use serde::Deserialize;
|
||||
|
||||
const KEYCHAIN_ID: &str = "tauri-build.keychain";
|
||||
const KEYCHAIN_PWD: &str = "tauri-build";
|
||||
|
||||
// Import certificate from ENV variables.
|
||||
// APPLE_CERTIFICATE is the p12 certificate base64 encoded.
|
||||
// By example you can use; openssl base64 -in MyCertificate.p12 -out MyCertificate-base64.txt
|
||||
// Then use the value of the base64 in APPLE_CERTIFICATE env variable.
|
||||
// You need to set APPLE_CERTIFICATE_PASSWORD to the password you set when you exported your certificate.
|
||||
// https://help.apple.com/xcode/mac/current/#/dev154b28f09 see: `Export a signing certificate`
|
||||
pub fn setup_keychain(
|
||||
certificate_encoded: OsString,
|
||||
certificate_password: OsString,
|
||||
) -> crate::Result<()> {
|
||||
// we delete any previous version of our keychain if present
|
||||
delete_keychain();
|
||||
log::info!("setup keychain from environment variables...");
|
||||
|
||||
let keychain_list_output = Command::new("security")
|
||||
.args(["list-keychain", "-d", "user"])
|
||||
.output()?;
|
||||
|
||||
let tmp_dir = tempfile::tempdir()?;
|
||||
let cert_path = tmp_dir
|
||||
.path()
|
||||
.join("cert.p12")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let cert_path_tmp = tmp_dir
|
||||
.path()
|
||||
.join("cert.p12.tmp")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let certificate_encoded = certificate_encoded
|
||||
.to_str()
|
||||
.expect("failed to convert APPLE_CERTIFICATE to string")
|
||||
.as_bytes();
|
||||
|
||||
let certificate_password = certificate_password
|
||||
.to_str()
|
||||
.expect("failed to convert APPLE_CERTIFICATE_PASSWORD to string")
|
||||
.to_string();
|
||||
|
||||
// as certificate contain whitespace decoding may be broken
|
||||
// https://github.com/marshallpierce/rust-base64/issues/105
|
||||
// we'll use builtin base64 command from the OS
|
||||
let mut tmp_cert = File::create(cert_path_tmp.clone())?;
|
||||
tmp_cert.write_all(certificate_encoded)?;
|
||||
|
||||
Command::new("base64")
|
||||
.args(["--decode", "-i", &cert_path_tmp, "-o", &cert_path])
|
||||
.output_ok()
|
||||
.context("failed to decode certificate")?;
|
||||
|
||||
Command::new("security")
|
||||
.args(["create-keychain", "-p", KEYCHAIN_PWD, KEYCHAIN_ID])
|
||||
.output_ok()
|
||||
.context("failed to create keychain")?;
|
||||
|
||||
Command::new("security")
|
||||
.args(["unlock-keychain", "-p", KEYCHAIN_PWD, KEYCHAIN_ID])
|
||||
.output_ok()
|
||||
.context("failed to set unlock keychain")?;
|
||||
|
||||
Command::new("security")
|
||||
.args([
|
||||
"import",
|
||||
&cert_path,
|
||||
"-k",
|
||||
KEYCHAIN_ID,
|
||||
"-P",
|
||||
&certificate_password,
|
||||
"-T",
|
||||
"/usr/bin/codesign",
|
||||
"-T",
|
||||
"/usr/bin/pkgbuild",
|
||||
"-T",
|
||||
"/usr/bin/productbuild",
|
||||
])
|
||||
.output_ok()
|
||||
.context("failed to import keychain certificate")?;
|
||||
|
||||
Command::new("security")
|
||||
.args(["set-keychain-settings", "-t", "3600", "-u", KEYCHAIN_ID])
|
||||
.output_ok()
|
||||
.context("failed to set keychain settings")?;
|
||||
|
||||
Command::new("security")
|
||||
.args([
|
||||
"set-key-partition-list",
|
||||
"-S",
|
||||
"apple-tool:,apple:,codesign:",
|
||||
"-s",
|
||||
"-k",
|
||||
KEYCHAIN_PWD,
|
||||
KEYCHAIN_ID,
|
||||
])
|
||||
.output_ok()
|
||||
.context("failed to set keychain settings")?;
|
||||
|
||||
let current_keychains = String::from_utf8_lossy(&keychain_list_output.stdout)
|
||||
.split('\n')
|
||||
.map(|line| {
|
||||
line
|
||||
.trim_matches(|c: char| c.is_whitespace() || c == '"')
|
||||
.to_string()
|
||||
})
|
||||
.filter(|l| !l.is_empty())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
Command::new("security")
|
||||
.args(["list-keychain", "-d", "user", "-s"])
|
||||
.args(current_keychains)
|
||||
.arg(KEYCHAIN_ID)
|
||||
.output_ok()
|
||||
.context("failed to list keychain")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_keychain() {
|
||||
// delete keychain if needed and skip any error
|
||||
let _ = Command::new("security")
|
||||
.arg("delete-keychain")
|
||||
.arg(KEYCHAIN_ID)
|
||||
.output_ok();
|
||||
}
|
||||
use crate::Settings;
|
||||
|
||||
pub struct SignTarget {
|
||||
pub path: PathBuf,
|
||||
pub is_an_executable: bool,
|
||||
}
|
||||
|
||||
pub fn sign(targets: Vec<SignTarget>, identity: &str, settings: &Settings) -> crate::Result<()> {
|
||||
pub fn sign(
|
||||
targets: Vec<SignTarget>,
|
||||
identity: &str,
|
||||
settings: &Settings,
|
||||
) -> crate::Result<tauri_macos_sign::Keychain> {
|
||||
log::info!(action = "Signing"; "with identity \"{}\"", identity);
|
||||
|
||||
let setup_keychain = if let (Some(certificate_encoded), Some(certificate_password)) = (
|
||||
let keychain = if let (Some(certificate_encoded), Some(certificate_password)) = (
|
||||
var_os("APPLE_CERTIFICATE"),
|
||||
var_os("APPLE_CERTIFICATE_PASSWORD"),
|
||||
) {
|
||||
// setup keychain allow you to import your certificate
|
||||
// for CI build
|
||||
setup_keychain(certificate_encoded, certificate_password)?;
|
||||
true
|
||||
tauri_macos_sign::Keychain::with_certificate(&certificate_encoded, &certificate_password)?
|
||||
} else {
|
||||
false
|
||||
tauri_macos_sign::Keychain::with_signing_identity(identity)
|
||||
};
|
||||
|
||||
log::info!("Signing app bundle...");
|
||||
|
||||
for target in targets {
|
||||
try_sign(
|
||||
target.path,
|
||||
identity,
|
||||
settings,
|
||||
target.is_an_executable,
|
||||
setup_keychain,
|
||||
keychain.sign(
|
||||
&target.path,
|
||||
settings.macos().entitlements.as_ref().map(Path::new),
|
||||
target.is_an_executable && settings.macos().hardened_runtime,
|
||||
)?;
|
||||
}
|
||||
|
||||
if setup_keychain {
|
||||
// delete the keychain again after signing
|
||||
delete_keychain();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_sign(
|
||||
path_to_sign: PathBuf,
|
||||
identity: &str,
|
||||
settings: &Settings,
|
||||
is_an_executable: bool,
|
||||
tauri_keychain: bool,
|
||||
) -> crate::Result<()> {
|
||||
log::info!(action = "Signing"; "{}", path_to_sign.display());
|
||||
|
||||
let mut args = vec!["--force", "-s", identity];
|
||||
|
||||
if tauri_keychain {
|
||||
args.push("--keychain");
|
||||
args.push(KEYCHAIN_ID);
|
||||
}
|
||||
|
||||
if let Some(entitlements_path) = &settings.macos().entitlements {
|
||||
log::info!("using entitlements file at {}", entitlements_path);
|
||||
args.push("--entitlements");
|
||||
args.push(entitlements_path);
|
||||
}
|
||||
|
||||
// add runtime flag by default
|
||||
|
||||
if is_an_executable && settings.macos().hardened_runtime {
|
||||
args.push("--options");
|
||||
args.push("runtime");
|
||||
}
|
||||
|
||||
Command::new("codesign")
|
||||
.args(args)
|
||||
.arg(path_to_sign)
|
||||
.output_ok()
|
||||
.context("failed to sign app")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct NotarytoolSubmitOutput {
|
||||
id: String,
|
||||
status: String,
|
||||
message: String,
|
||||
Ok(keychain)
|
||||
}
|
||||
|
||||
pub fn notarize(
|
||||
keychain: &tauri_macos_sign::Keychain,
|
||||
app_bundle_path: PathBuf,
|
||||
auth: NotarizeAuth,
|
||||
settings: &Settings,
|
||||
credentials: &tauri_macos_sign::AppleNotarizationCredentials,
|
||||
) -> crate::Result<()> {
|
||||
let bundle_stem = app_bundle_path
|
||||
.file_stem()
|
||||
.expect("failed to get bundle filename");
|
||||
|
||||
let tmp_dir = tempfile::tempdir()?;
|
||||
let zip_path = tmp_dir
|
||||
.path()
|
||||
.join(format!("{}.zip", bundle_stem.to_string_lossy()));
|
||||
let zip_args = vec![
|
||||
"-c",
|
||||
"-k",
|
||||
"--keepParent",
|
||||
"--sequesterRsrc",
|
||||
app_bundle_path
|
||||
.to_str()
|
||||
.expect("failed to convert bundle_path to string"),
|
||||
zip_path
|
||||
.to_str()
|
||||
.expect("failed to convert zip_path to string"),
|
||||
];
|
||||
|
||||
// use ditto to create a PKZip almost identical to Finder
|
||||
// this remove almost 99% of false alarm in notarization
|
||||
Command::new("ditto")
|
||||
.args(zip_args)
|
||||
.output_ok()
|
||||
.context("failed to zip app with ditto")?;
|
||||
|
||||
// sign the zip file
|
||||
if let Some(identity) = &settings.macos().signing_identity {
|
||||
sign(
|
||||
vec![SignTarget {
|
||||
path: zip_path.clone(),
|
||||
is_an_executable: false,
|
||||
}],
|
||||
identity,
|
||||
settings,
|
||||
)?;
|
||||
};
|
||||
|
||||
let notarize_args = vec![
|
||||
"notarytool",
|
||||
"submit",
|
||||
zip_path
|
||||
.to_str()
|
||||
.expect("failed to convert zip_path to string"),
|
||||
"--wait",
|
||||
"--output-format",
|
||||
"json",
|
||||
];
|
||||
|
||||
log::info!(action = "Notarizing"; "{}", app_bundle_path.display());
|
||||
|
||||
let output = Command::new("xcrun")
|
||||
.args(notarize_args)
|
||||
.notarytool_args(&auth)
|
||||
.output_ok()
|
||||
.context("failed to upload app to Apple's notarization servers.")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("failed to notarize app").into());
|
||||
}
|
||||
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
if let Ok(submit_output) = serde_json::from_str::<NotarytoolSubmitOutput>(&output_str) {
|
||||
let log_message = format!(
|
||||
"Finished with status {} for id {} ({})",
|
||||
submit_output.status, submit_output.id, submit_output.message
|
||||
);
|
||||
if submit_output.status == "Accepted" {
|
||||
log::info!(action = "Notarizing"; "{}", log_message);
|
||||
staple_app(app_bundle_path)?;
|
||||
Ok(())
|
||||
} else if let Ok(output) = Command::new("xcrun")
|
||||
.args(["notarytool", "log"])
|
||||
.arg(&submit_output.id)
|
||||
.notarytool_args(&auth)
|
||||
.output_ok()
|
||||
{
|
||||
Err(
|
||||
anyhow::anyhow!(
|
||||
"{log_message}\nLog:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("{log_message}").into())
|
||||
}
|
||||
} else {
|
||||
Err(anyhow::anyhow!("failed to parse notarytool output as JSON: `{output_str}`").into())
|
||||
}
|
||||
}
|
||||
|
||||
fn staple_app(mut app_bundle_path: PathBuf) -> crate::Result<()> {
|
||||
let app_bundle_path_clone = app_bundle_path.clone();
|
||||
let filename = app_bundle_path_clone
|
||||
.file_name()
|
||||
.expect("failed to get bundle filename")
|
||||
.to_str()
|
||||
.expect("failed to convert bundle filename to string");
|
||||
|
||||
app_bundle_path.pop();
|
||||
|
||||
Command::new("xcrun")
|
||||
.args(vec!["stapler", "staple", "-v", filename])
|
||||
.current_dir(app_bundle_path)
|
||||
.output_ok()
|
||||
.context("failed to staple app.")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub enum NotarizeAuth {
|
||||
AppleId {
|
||||
apple_id: OsString,
|
||||
password: OsString,
|
||||
team_id: OsString,
|
||||
},
|
||||
ApiKey {
|
||||
key: OsString,
|
||||
key_path: PathBuf,
|
||||
issuer: OsString,
|
||||
},
|
||||
}
|
||||
|
||||
pub trait NotarytoolCmdExt {
|
||||
fn notarytool_args(&mut self, auth: &NotarizeAuth) -> &mut Self;
|
||||
}
|
||||
|
||||
impl NotarytoolCmdExt for Command {
|
||||
fn notarytool_args(&mut self, auth: &NotarizeAuth) -> &mut Self {
|
||||
match auth {
|
||||
NotarizeAuth::AppleId {
|
||||
apple_id,
|
||||
password,
|
||||
team_id,
|
||||
} => self
|
||||
.arg("--apple-id")
|
||||
.arg(apple_id)
|
||||
.arg("--password")
|
||||
.arg(password)
|
||||
.arg("--team-id")
|
||||
.arg(team_id),
|
||||
NotarizeAuth::ApiKey {
|
||||
key,
|
||||
key_path,
|
||||
issuer,
|
||||
} => self
|
||||
.arg("--key-id")
|
||||
.arg(key)
|
||||
.arg("--key")
|
||||
.arg(key_path)
|
||||
.arg("--issuer")
|
||||
.arg(issuer),
|
||||
}
|
||||
}
|
||||
tauri_macos_sign::notarize(keychain, &app_bundle_path, credentials).map_err(Into::into)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@ -402,26 +65,29 @@ pub enum NotarizeAuthError {
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
pub fn notarize_auth() -> Result<NotarizeAuth, NotarizeAuthError> {
|
||||
pub fn notarize_auth() -> Result<tauri_macos_sign::AppleNotarizationCredentials, NotarizeAuthError>
|
||||
{
|
||||
match (
|
||||
var_os("APPLE_ID"),
|
||||
var_os("APPLE_PASSWORD"),
|
||||
var_os("APPLE_TEAM_ID"),
|
||||
) {
|
||||
(Some(apple_id), Some(password), Some(team_id)) => Ok(NotarizeAuth::AppleId {
|
||||
(Some(apple_id), Some(password), Some(team_id)) => {
|
||||
Ok(tauri_macos_sign::AppleNotarizationCredentials::AppleId {
|
||||
apple_id,
|
||||
password,
|
||||
team_id,
|
||||
}),
|
||||
})
|
||||
}
|
||||
(Some(_apple_id), Some(_password), None) => Err(NotarizeAuthError::MissingTeamId),
|
||||
_ => {
|
||||
match (var_os("APPLE_API_KEY"), var_os("APPLE_API_ISSUER"), var("APPLE_API_KEY_PATH")) {
|
||||
(Some(key), Some(issuer), Ok(key_path)) => {
|
||||
Ok(NotarizeAuth::ApiKey { key, key_path: key_path.into(), issuer })
|
||||
(Some(key_id), Some(issuer), Ok(key_path)) => {
|
||||
Ok(tauri_macos_sign::AppleNotarizationCredentials::ApiKey { key_id, key: tauri_macos_sign::ApiKey::Path( key_path.into()), issuer })
|
||||
},
|
||||
(Some(key), Some(issuer), Err(_)) => {
|
||||
(Some(key_id), Some(issuer), Err(_)) => {
|
||||
let mut api_key_file_name = OsString::from("AuthKey_");
|
||||
api_key_file_name.push(&key);
|
||||
api_key_file_name.push(&key_id);
|
||||
api_key_file_name.push(".p8");
|
||||
let mut key_path = None;
|
||||
|
||||
@ -440,7 +106,7 @@ pub fn notarize_auth() -> Result<NotarizeAuth, NotarizeAuthError> {
|
||||
}
|
||||
|
||||
if let Some(key_path) = key_path {
|
||||
Ok(NotarizeAuth::ApiKey { key, key_path, issuer })
|
||||
Ok(tauri_macos_sign::AppleNotarizationCredentials::ApiKey { key_id, key: tauri_macos_sign::ApiKey::Path(key_path), issuer })
|
||||
} else {
|
||||
Err(anyhow::anyhow!("could not find API key file. Please set the APPLE_API_KEY_PATH environment variables to the path to the {api_key_file_name:?} file").into())
|
||||
}
|
||||
|
39
tooling/cli/Cargo.lock
generated
39
tooling/cli/Cargo.lock
generated
@ -1161,6 +1161,16 @@ dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
@ -1173,6 +1183,17 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dsa"
|
||||
version = "0.6.3"
|
||||
@ -5081,6 +5102,7 @@ dependencies = [
|
||||
"strsim 0.11.0",
|
||||
"tar",
|
||||
"tauri-icns",
|
||||
"tauri-macos-sign",
|
||||
"tauri-utils 2.0.0-beta.18",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
@ -5151,6 +5173,7 @@ dependencies = [
|
||||
"sublime_fuzzy",
|
||||
"tauri-bundler",
|
||||
"tauri-icns",
|
||||
"tauri-macos-sign",
|
||||
"tauri-utils 1.5.4",
|
||||
"tauri-utils 2.0.0-beta.18",
|
||||
"tokio",
|
||||
@ -5182,6 +5205,22 @@ dependencies = [
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macos-sign"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dirs-next",
|
||||
"once-cell-regex",
|
||||
"os_pipe",
|
||||
"plist",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"x509-certificate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.5.4"
|
||||
|
@ -112,6 +112,7 @@ libc = "0.2"
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
plist = "1"
|
||||
tauri-macos-sign = { version = "0.0.0", path = "../macos-sign" }
|
||||
|
||||
[features]
|
||||
default = [ "rustls" ]
|
||||
|
@ -24,6 +24,7 @@ use cargo_mobile2::{
|
||||
use handlebars::{
|
||||
Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError, RenderErrorReason,
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
use std::{env::var_os, path::PathBuf};
|
||||
|
||||
@ -35,7 +36,22 @@ pub fn command(
|
||||
) -> Result<()> {
|
||||
let wrapper = TextWrapper::default();
|
||||
|
||||
exec(target, &wrapper, ci, reinstall_deps, skip_targets_install)
|
||||
let tauri_init_config = TauriInitConfig {
|
||||
#[cfg(target_os = "macos")]
|
||||
ios: {
|
||||
let (keychain, provisioning_profile) = super::ios::signing_from_env()?;
|
||||
super::ios::init_config(keychain.as_ref(), provisioning_profile.as_ref())?
|
||||
},
|
||||
};
|
||||
|
||||
exec(
|
||||
target,
|
||||
&wrapper,
|
||||
&tauri_init_config,
|
||||
ci,
|
||||
reinstall_deps,
|
||||
skip_targets_install,
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("{:#}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
@ -77,9 +93,33 @@ pub fn configure_cargo(
|
||||
dot_cargo.write(app).map_err(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Serialize)]
|
||||
pub enum CodeSignStyle {
|
||||
Manual,
|
||||
Automatic,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IosInitConfig {
|
||||
pub code_sign_style: CodeSignStyle,
|
||||
pub code_sign_identity: Option<String>,
|
||||
pub team_id: Option<String>,
|
||||
pub provisioning_profile_uuid: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct TauriInitConfig {
|
||||
#[cfg(target_os = "macos")]
|
||||
ios: IosInitConfig,
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
target: Target,
|
||||
wrapper: &TextWrapper,
|
||||
tauri_init_config: &TauriInitConfig,
|
||||
#[allow(unused_variables)] non_interactive: bool,
|
||||
#[allow(unused_variables)] reinstall_deps: bool,
|
||||
skip_targets_install: bool,
|
||||
@ -93,6 +133,8 @@ pub fn exec(
|
||||
|
||||
let (handlebars, mut map) = handlebars(&app);
|
||||
|
||||
map.insert("tauri", tauri_init_config);
|
||||
|
||||
let mut args = std::env::args_os();
|
||||
|
||||
let (binary, mut build_args) = args
|
||||
|
@ -17,7 +17,7 @@ use crate::{
|
||||
mobile::{write_options, CliOptions},
|
||||
ConfigValue, Result,
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
use clap::{ArgAction, Parser, ValueEnum};
|
||||
|
||||
use anyhow::Context;
|
||||
use cargo_mobile2::{
|
||||
@ -63,6 +63,41 @@ pub struct Options {
|
||||
/// Skip prompting for values
|
||||
#[clap(long, env = "CI")]
|
||||
pub ci: bool,
|
||||
/// Describes how Xcode should export the archive.
|
||||
///
|
||||
/// Use this to create a package ready for the App Store (app-store-connect option) or TestFlight (release-testing option).
|
||||
#[clap(long, value_enum)]
|
||||
pub export_method: Option<ExportMethod>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum ExportMethod {
|
||||
AppStoreConnect,
|
||||
ReleaseTesting,
|
||||
Debugging,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExportMethod {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::AppStoreConnect => write!(f, "app-store-connect"),
|
||||
Self::ReleaseTesting => write!(f, "release-testing"),
|
||||
Self::Debugging => write!(f, "debugging"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ExportMethod {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"app-store-connect" => Ok(Self::AppStoreConnect),
|
||||
"release-testing" => Ok(Self::ReleaseTesting),
|
||||
"debugging" => Ok(Self::Debugging),
|
||||
_ => Err("unknown ios target"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Options> for BuildOptions {
|
||||
@ -129,9 +164,9 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
.join(config.scheme())
|
||||
.join("Info.plist");
|
||||
merge_plist(
|
||||
&[
|
||||
tauri_path.join("Info.plist"),
|
||||
tauri_path.join("Info.ios.plist"),
|
||||
vec![
|
||||
tauri_path.join("Info.plist").into(),
|
||||
tauri_path.join("Info.ios.plist").into(),
|
||||
],
|
||||
&info_plist_path,
|
||||
)?;
|
||||
@ -139,6 +174,22 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
let mut env = env()?;
|
||||
configure_cargo(&app, None)?;
|
||||
|
||||
let (keychain, provisioning_profile) = super::signing_from_env()?;
|
||||
let init_config = super::init_config(keychain.as_ref(), provisioning_profile.as_ref())?;
|
||||
if let Some(export_options_plist) =
|
||||
create_export_options(&app, &init_config, options.export_method)
|
||||
{
|
||||
let export_options_plist_path = config.project_dir().join("ExportOptions.plist");
|
||||
|
||||
merge_plist(
|
||||
vec![
|
||||
export_options_plist_path.clone().into(),
|
||||
export_options_plist.into(),
|
||||
],
|
||||
&export_options_plist_path,
|
||||
)?;
|
||||
}
|
||||
|
||||
let open = options.open;
|
||||
let _handle = run_build(
|
||||
interface,
|
||||
@ -157,6 +208,41 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_export_options(
|
||||
app: &cargo_mobile2::config::app::App,
|
||||
config: &super::super::init::IosInitConfig,
|
||||
export_method: Option<ExportMethod>,
|
||||
) -> Option<plist::Value> {
|
||||
let mut plist = plist::Dictionary::new();
|
||||
|
||||
if let Some(method) = export_method {
|
||||
plist.insert("method".to_string(), method.to_string().into());
|
||||
}
|
||||
|
||||
if config.code_sign_identity.is_some() || config.provisioning_profile_uuid.is_some() {
|
||||
plist.insert("signingStyle".to_string(), "manual".into());
|
||||
}
|
||||
|
||||
if let Some(identity) = &config.code_sign_identity {
|
||||
plist.insert("signingCertificate".to_string(), identity.clone().into());
|
||||
}
|
||||
|
||||
if let Some(id) = &config.team_id {
|
||||
plist.insert("teamID".to_string(), id.clone().into());
|
||||
}
|
||||
|
||||
if let Some(profile_uuid) = &config.provisioning_profile_uuid {
|
||||
let mut provisioning_profiles = plist::Dictionary::new();
|
||||
provisioning_profiles.insert(app.reverse_identifier(), profile_uuid.clone().into());
|
||||
plist.insert(
|
||||
"provisioningProfiles".to_string(),
|
||||
provisioning_profiles.into(),
|
||||
);
|
||||
}
|
||||
|
||||
(!plist.is_empty()).then(|| plist.into())
|
||||
}
|
||||
|
||||
fn run_build(
|
||||
interface: AppInterface,
|
||||
options: Options,
|
||||
|
@ -148,9 +148,9 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
.join(config.scheme())
|
||||
.join("Info.plist");
|
||||
merge_plist(
|
||||
&[
|
||||
tauri_path.join("Info.plist"),
|
||||
tauri_path.join("Info.ios.plist"),
|
||||
vec![
|
||||
tauri_path.join("Info.plist").into(),
|
||||
tauri_path.join("Info.ios.plist").into(),
|
||||
],
|
||||
&info_plist_path,
|
||||
)?;
|
||||
|
@ -30,7 +30,7 @@ use super::{
|
||||
use crate::{helpers::config::Config as TauriConfig, Result};
|
||||
|
||||
use std::{
|
||||
env::set_var,
|
||||
env::{set_var, var_os},
|
||||
fs::create_dir_all,
|
||||
path::{Path, PathBuf},
|
||||
thread::sleep,
|
||||
@ -275,16 +275,36 @@ fn inject_assets(config: &AppleConfig) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn merge_plist(src: &[PathBuf], dest: &Path) -> Result<()> {
|
||||
enum PlistKind {
|
||||
Path(PathBuf),
|
||||
Plist(plist::Value),
|
||||
}
|
||||
|
||||
impl From<PathBuf> for PlistKind {
|
||||
fn from(p: PathBuf) -> Self {
|
||||
Self::Path(p)
|
||||
}
|
||||
}
|
||||
impl From<plist::Value> for PlistKind {
|
||||
fn from(p: plist::Value) -> Self {
|
||||
Self::Plist(p)
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_plist(src: Vec<PlistKind>, dest: &Path) -> Result<()> {
|
||||
let mut dest_plist = None;
|
||||
|
||||
for src_path in src {
|
||||
if let Ok(src_plist) = plist::Value::from_file(src_path) {
|
||||
for plist_kind in src {
|
||||
let plist = match plist_kind {
|
||||
PlistKind::Path(p) => plist::Value::from_file(p),
|
||||
PlistKind::Plist(v) => Ok(v),
|
||||
};
|
||||
if let Ok(src_plist) = plist {
|
||||
if dest_plist.is_none() {
|
||||
dest_plist.replace(plist::Value::from_file(dest)?);
|
||||
}
|
||||
|
||||
let plist = dest_plist.as_mut().expect("Info.plist not loaded");
|
||||
let plist = dest_plist.as_mut().expect("plist not loaded");
|
||||
if let Some(plist) = plist.as_dictionary_mut() {
|
||||
if let Some(dict) = src_plist.into_dictionary() {
|
||||
for (key, value) in dict {
|
||||
@ -301,3 +321,40 @@ fn merge_plist(src: &[PathBuf], dest: &Path) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn signing_from_env() -> Result<(
|
||||
Option<tauri_macos_sign::Keychain>,
|
||||
Option<tauri_macos_sign::ProvisioningProfile>,
|
||||
)> {
|
||||
let keychain = if let (Some(certificate), Some(certificate_password)) = (
|
||||
var_os("IOS_CERTIFICATE"),
|
||||
var_os("IOS_CERTIFICATE_PASSWORD"),
|
||||
) {
|
||||
tauri_macos_sign::Keychain::with_certificate(&certificate, &certificate_password).map(Some)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let provisioning_profile = if let Some(provisioning_profile) = var_os("IOS_MOBILE_PROVISION") {
|
||||
tauri_macos_sign::ProvisioningProfile::from_base64(&provisioning_profile).map(Some)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok((keychain, provisioning_profile))
|
||||
}
|
||||
|
||||
pub fn init_config(
|
||||
keychain: Option<&tauri_macos_sign::Keychain>,
|
||||
provisioning_profile: Option<&tauri_macos_sign::ProvisioningProfile>,
|
||||
) -> Result<super::init::IosInitConfig> {
|
||||
Ok(super::init::IosInitConfig {
|
||||
code_sign_style: if keychain.is_some() && provisioning_profile.is_some() {
|
||||
super::init::CodeSignStyle::Manual
|
||||
} else {
|
||||
super::init::CodeSignStyle::Automatic
|
||||
},
|
||||
code_sign_identity: keychain.map(|k| k.signing_identity()),
|
||||
team_id: keychain.and_then(|k| k.team_id().map(ToString::to_string)),
|
||||
provisioning_profile_uuid: provisioning_profile.and_then(|p| p.uuid().ok()),
|
||||
})
|
||||
}
|
||||
|
@ -15,6 +15,13 @@ settingGroups:
|
||||
{{#if apple.development-team}}
|
||||
DEVELOPMENT_TEAM: {{apple.development-team}}
|
||||
{{/if}}
|
||||
CODE_SIGN_STYLE: {{tauri.ios.code-sign-style}}
|
||||
{{#if tauri.ios.code-sign-identity}}
|
||||
CODE_SIGN_IDENTITY: "{{tauri.ios.code-sign-identity}}"
|
||||
{{/if}}
|
||||
{{#if tauri.ios.provisioning-profile-uuid}}
|
||||
PROVISIONING_PROFILE_SPECIFIER: "{{tauri.ios.provisioning-profile-uuid}}"
|
||||
{{/if}}
|
||||
targetTemplates:
|
||||
app:
|
||||
type: application
|
||||
|
869
tooling/macos-sign/Cargo.lock
generated
Normal file
869
tooling/macos-sign/Cargo.lock
generated
Normal file
@ -0,0 +1,869 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bcder"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c627747a6774aab38beb35990d88309481378558875a41da1a4b2e373c906ef0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "line-wrap"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once-cell-regex"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3de7e389a5043420c8f2b95ed03f3f104ad6f4c41f7d7e27298f033abc253e8"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plist"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"indexmap",
|
||||
"line-wrap",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macos-sign"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dirs-next",
|
||||
"once-cell-regex",
|
||||
"os_pipe",
|
||||
"plist",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"x509-certificate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
|
||||
[[package]]
|
||||
name = "x509-certificate"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66534846dec7a11d7c50a74b7cdb208b9a581cad890b7866430d438455847c85"
|
||||
dependencies = [
|
||||
"bcder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"der",
|
||||
"hex",
|
||||
"pem",
|
||||
"ring",
|
||||
"signature",
|
||||
"spki",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
24
tooling/macos-sign/Cargo.toml
Normal file
24
tooling/macos-sign/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
workspace = {}
|
||||
|
||||
[package]
|
||||
name = "tauri-macos-sign"
|
||||
version = "0.0.0"
|
||||
authors = ["Tauri Programme within The Commons Conservancy"]
|
||||
license = "Apache-2.0 OR MIT"
|
||||
keywords = ["codesign", "signing", "macos", "ios", "tauri"]
|
||||
repository = "https://github.com/tauri-apps/tauri"
|
||||
description = "Code signing utilities for macOS and iOS apps"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tempfile = "3"
|
||||
x509-certificate = "0.23"
|
||||
once-cell-regex = "0.2"
|
||||
os_pipe = "1"
|
||||
plist = "1"
|
||||
rand = "0.8"
|
||||
dirs-next = "2"
|
3
tooling/macos-sign/README.md
Normal file
3
tooling/macos-sign/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Tauri MacOS Sign
|
||||
|
||||
Utilities for setting up macOS certificates, code signing and notarization for macOS and iOS apps.
|
216
tooling/macos-sign/src/keychain.rs
Normal file
216
tooling/macos-sign/src/keychain.rs
Normal file
@ -0,0 +1,216 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use crate::assert_command;
|
||||
use anyhow::Result;
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
|
||||
mod identity;
|
||||
|
||||
pub use identity::Team;
|
||||
|
||||
pub enum SigningIdentity {
|
||||
Team(Team),
|
||||
Identifier(String),
|
||||
}
|
||||
|
||||
pub struct Keychain {
|
||||
// none means the default keychain must be used
|
||||
path: Option<PathBuf>,
|
||||
signing_identity: SigningIdentity,
|
||||
}
|
||||
|
||||
impl Drop for Keychain {
|
||||
fn drop(&mut self) {
|
||||
if let Some(path) = &self.path {
|
||||
let _ = Command::new("security")
|
||||
.arg("delete-keychain")
|
||||
.arg(path)
|
||||
.status();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Keychain {
|
||||
/// Use a certificate in the default keychain.
|
||||
pub fn with_signing_identity(identity: impl Into<String>) -> Self {
|
||||
Self {
|
||||
path: None,
|
||||
signing_identity: SigningIdentity::Identifier(identity.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Import certificate from base64 string.
|
||||
/// certificate_encoded is the p12 certificate base64 encoded.
|
||||
/// By example you can use; openssl base64 -in MyCertificate.p12 -out MyCertificate-base64.txt
|
||||
/// Then use the value of the base64 as `certificate_encoded`.
|
||||
/// You need to set certificate_password to the password you set when you exported your certificate.
|
||||
/// https://help.apple.com/xcode/mac/current/#/dev154b28f09 see: `Export a signing certificate`
|
||||
pub fn with_certificate(
|
||||
certificate_encoded: &OsString,
|
||||
certificate_password: &OsString,
|
||||
) -> Result<Self> {
|
||||
let home_dir =
|
||||
dirs_next::home_dir().ok_or_else(|| anyhow::anyhow!("failed to resolve home dir"))?;
|
||||
let keychain_path = home_dir.join("Library").join("Keychains").join(format!(
|
||||
"{}.keychain-db",
|
||||
Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
|
||||
));
|
||||
let keychain_password = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
|
||||
|
||||
let keychain_list_output = Command::new("security")
|
||||
.args(["list-keychain", "-d", "user"])
|
||||
.output()?;
|
||||
|
||||
let tmp_dir = tempfile::tempdir()?;
|
||||
let cert_path = tmp_dir.path().join("cert.p12");
|
||||
super::decode_base64(certificate_encoded, &cert_path)?;
|
||||
|
||||
assert_command(
|
||||
Command::new("security")
|
||||
.args(["create-keychain", "-p", &keychain_password])
|
||||
.arg(&keychain_path)
|
||||
.status(),
|
||||
"failed to create keychain",
|
||||
)?;
|
||||
|
||||
assert_command(
|
||||
Command::new("security")
|
||||
.args(["unlock-keychain", "-p", &keychain_password])
|
||||
.arg(&keychain_path)
|
||||
.status(),
|
||||
"failed to set unlock keychain",
|
||||
)?;
|
||||
|
||||
assert_command(
|
||||
Command::new("security")
|
||||
.arg("import")
|
||||
.arg(&cert_path)
|
||||
.arg("-P")
|
||||
.arg(certificate_password)
|
||||
.args([
|
||||
"-T",
|
||||
"/usr/bin/codesign",
|
||||
"-T",
|
||||
"/usr/bin/pkgbuild",
|
||||
"-T",
|
||||
"/usr/bin/productbuild",
|
||||
])
|
||||
.arg("-k")
|
||||
.arg(&keychain_path)
|
||||
.status(),
|
||||
"failed to import keychain certificate",
|
||||
)?;
|
||||
|
||||
assert_command(
|
||||
Command::new("security")
|
||||
.args(["set-keychain-settings", "-t", "3600", "-u"])
|
||||
.arg(&keychain_path)
|
||||
.status(),
|
||||
"failed to set keychain settings",
|
||||
)?;
|
||||
|
||||
assert_command(
|
||||
Command::new("security")
|
||||
.args([
|
||||
"set-key-partition-list",
|
||||
"-S",
|
||||
"apple-tool:,apple:,codesign:",
|
||||
"-s",
|
||||
"-k",
|
||||
&keychain_password,
|
||||
])
|
||||
.arg(&keychain_path)
|
||||
.status(),
|
||||
"failed to set keychain settings",
|
||||
)?;
|
||||
|
||||
let current_keychains = String::from_utf8_lossy(&keychain_list_output.stdout)
|
||||
.split('\n')
|
||||
.map(|line| {
|
||||
line
|
||||
.trim_matches(|c: char| c.is_whitespace() || c == '"')
|
||||
.to_string()
|
||||
})
|
||||
.filter(|l| !l.is_empty())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
assert_command(
|
||||
Command::new("security")
|
||||
.args(["list-keychain", "-d", "user", "-s"])
|
||||
.args(current_keychains)
|
||||
.arg(&keychain_path)
|
||||
.status(),
|
||||
"failed to list keychain",
|
||||
)?;
|
||||
|
||||
let signing_identity = identity::list(&keychain_path)
|
||||
.map(|l| l.first().cloned())?
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to resolve signing identity"))?;
|
||||
|
||||
Ok(Self {
|
||||
path: Some(keychain_path),
|
||||
signing_identity: SigningIdentity::Team(signing_identity),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn signing_identity(&self) -> String {
|
||||
match &self.signing_identity {
|
||||
SigningIdentity::Team(t) => t.certificate_name(),
|
||||
SigningIdentity::Identifier(i) => i.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn team_id(&self) -> Option<&str> {
|
||||
match &self.signing_identity {
|
||||
SigningIdentity::Team(t) => Some(&t.id),
|
||||
SigningIdentity::Identifier(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign(
|
||||
&self,
|
||||
path: &Path,
|
||||
entitlements_path: Option<&Path>,
|
||||
hardened_runtime: bool,
|
||||
) -> Result<()> {
|
||||
let identity = match &self.signing_identity {
|
||||
SigningIdentity::Team(t) => t.certificate_name(),
|
||||
SigningIdentity::Identifier(i) => i.clone(),
|
||||
};
|
||||
println!("Signing with identity \"{}\"", identity);
|
||||
|
||||
println!("Signing {}", path.display());
|
||||
|
||||
let mut args = vec!["--force", "-s", &identity];
|
||||
|
||||
if hardened_runtime {
|
||||
args.push("--options");
|
||||
args.push("runtime");
|
||||
}
|
||||
|
||||
let mut codesign = Command::new("codesign");
|
||||
codesign.args(args);
|
||||
if let Some(p) = &self.path {
|
||||
codesign.arg("--keychain").arg(p);
|
||||
}
|
||||
|
||||
if let Some(entitlements_path) = entitlements_path {
|
||||
codesign.arg("--entitlements");
|
||||
codesign.arg(entitlements_path);
|
||||
}
|
||||
|
||||
codesign.arg(path);
|
||||
|
||||
assert_command(codesign.status(), "failed to sign app")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
117
tooling/macos-sign/src/keychain/identity.rs
Normal file
117
tooling/macos-sign/src/keychain/identity.rs
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use anyhow::Context;
|
||||
use once_cell_regex::regex;
|
||||
use std::{collections::BTreeSet, path::Path, process::Command};
|
||||
use x509_certificate::certificate::X509Certificate;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
fn get_pem_list(keychain_path: &Path, name_substr: &str) -> std::io::Result<std::process::Output> {
|
||||
Command::new("security")
|
||||
.arg("find-certificate")
|
||||
.args(["-p", "-a"])
|
||||
.arg("-c")
|
||||
.arg(name_substr)
|
||||
.arg(keychain_path)
|
||||
.stderr(os_pipe::dup_stderr().unwrap())
|
||||
.output()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Team {
|
||||
pub name: String,
|
||||
pub certificate_name: String,
|
||||
pub id: String,
|
||||
pub cert_prefix: &'static str,
|
||||
}
|
||||
|
||||
impl Team {
|
||||
fn from_x509(cert_prefix: &'static str, cert: X509Certificate) -> Result<Self> {
|
||||
let common_name = cert
|
||||
.subject_common_name()
|
||||
.ok_or_else(|| anyhow::anyhow!("skipping cert, missing common name"))?;
|
||||
|
||||
let organization = cert
|
||||
.subject_name()
|
||||
.iter_organization()
|
||||
.next()
|
||||
.and_then(|v| v.to_string().ok());
|
||||
|
||||
let name = if let Some(organization) = organization {
|
||||
println!(
|
||||
"found cert {:?} with organization {:?}",
|
||||
common_name, organization
|
||||
);
|
||||
organization
|
||||
} else {
|
||||
println!(
|
||||
"found cert {:?} but failed to get organization; falling back to displaying common name",
|
||||
common_name
|
||||
);
|
||||
regex!(r"Apple Develop\w+: (.*) \(.+\)")
|
||||
.captures(&common_name)
|
||||
.map(|caps| caps[1].to_owned())
|
||||
.unwrap_or_else(|| {
|
||||
println!("regex failed to capture nice part of name in cert {:?}; falling back to displaying full name", common_name);
|
||||
common_name.clone()
|
||||
})
|
||||
};
|
||||
|
||||
let id = cert
|
||||
.subject_name()
|
||||
.iter_organizational_unit()
|
||||
.next()
|
||||
.and_then(|v| v.to_string().ok())
|
||||
.ok_or_else(|| anyhow::anyhow!("skipping cert {common_name}: missing Organization Unit"))?;
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
certificate_name: common_name,
|
||||
id,
|
||||
cert_prefix,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn certificate_name(&self) -> String {
|
||||
self.certificate_name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list(keychain_path: &Path) -> Result<Vec<Team>> {
|
||||
let certs = {
|
||||
let mut certs = Vec::new();
|
||||
for cert_prefix in [
|
||||
"iOS Distribution:",
|
||||
"Apple Distribution:",
|
||||
"Developer ID Application:",
|
||||
"Mac App Distribution:",
|
||||
"Apple Development:",
|
||||
"iOS App Development:",
|
||||
"Mac Development:",
|
||||
] {
|
||||
let pem_list_out =
|
||||
get_pem_list(keychain_path, cert_prefix).context("Failed to call `security` command")?;
|
||||
let cert_list = X509Certificate::from_pem_multiple(pem_list_out.stdout)
|
||||
.context("Failed to parse X509 cert")?;
|
||||
certs.extend(cert_list.into_iter().map(|cert| (cert_prefix, cert)));
|
||||
}
|
||||
certs
|
||||
};
|
||||
Ok(
|
||||
certs
|
||||
.into_iter()
|
||||
.flat_map(|(cert_prefix, cert)| {
|
||||
Team::from_x509(cert_prefix, cert).map_err(|err| {
|
||||
eprintln!("{}", err);
|
||||
err
|
||||
})
|
||||
})
|
||||
// Silly way to sort this and ensure no dupes
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)
|
||||
}
|
251
tooling/macos-sign/src/lib.rs
Normal file
251
tooling/macos-sign/src/lib.rs
Normal file
@ -0,0 +1,251 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
|
||||
mod keychain;
|
||||
mod provisioning_profile;
|
||||
|
||||
pub use keychain::{Keychain, Team};
|
||||
pub use provisioning_profile::ProvisioningProfile;
|
||||
|
||||
pub enum ApiKey {
|
||||
Path(PathBuf),
|
||||
Raw(Vec<u8>),
|
||||
}
|
||||
|
||||
pub enum AppleNotarizationCredentials {
|
||||
AppleId {
|
||||
apple_id: OsString,
|
||||
password: OsString,
|
||||
team_id: OsString,
|
||||
},
|
||||
ApiKey {
|
||||
issuer: OsString,
|
||||
key_id: OsString,
|
||||
key: ApiKey,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct NotarytoolSubmitOutput {
|
||||
id: String,
|
||||
status: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
pub fn notarize(
|
||||
keychain: &Keychain,
|
||||
app_bundle_path: &Path,
|
||||
auth: &AppleNotarizationCredentials,
|
||||
) -> Result<()> {
|
||||
let bundle_stem = app_bundle_path
|
||||
.file_stem()
|
||||
.expect("failed to get bundle filename");
|
||||
|
||||
let tmp_dir = tempfile::tempdir()?;
|
||||
let zip_path = tmp_dir
|
||||
.path()
|
||||
.join(format!("{}.zip", bundle_stem.to_string_lossy()));
|
||||
let zip_args = vec![
|
||||
"-c",
|
||||
"-k",
|
||||
"--keepParent",
|
||||
"--sequesterRsrc",
|
||||
app_bundle_path
|
||||
.to_str()
|
||||
.expect("failed to convert bundle_path to string"),
|
||||
zip_path
|
||||
.to_str()
|
||||
.expect("failed to convert zip_path to string"),
|
||||
];
|
||||
|
||||
// use ditto to create a PKZip almost identical to Finder
|
||||
// this remove almost 99% of false alarm in notarization
|
||||
assert_command(
|
||||
Command::new("ditto").args(zip_args).status(),
|
||||
"failed to zip app with ditto",
|
||||
)?;
|
||||
|
||||
// sign the zip file
|
||||
keychain.sign(&zip_path, None, false)?;
|
||||
|
||||
let notarize_args = vec![
|
||||
"notarytool",
|
||||
"submit",
|
||||
zip_path
|
||||
.to_str()
|
||||
.expect("failed to convert zip_path to string"),
|
||||
"--wait",
|
||||
"--output-format",
|
||||
"json",
|
||||
];
|
||||
|
||||
println!("Notarizing {}", app_bundle_path.display());
|
||||
|
||||
let output = Command::new("xcrun")
|
||||
.args(notarize_args)
|
||||
.notarytool_args(auth, tmp_dir.path())?
|
||||
.output()
|
||||
.context("failed to upload app to Apple's notarization servers.")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("failed to notarize app"));
|
||||
}
|
||||
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
if let Ok(submit_output) = serde_json::from_str::<NotarytoolSubmitOutput>(&output_str) {
|
||||
let log_message = format!(
|
||||
"Finished with status {} for id {} ({})",
|
||||
submit_output.status, submit_output.id, submit_output.message
|
||||
);
|
||||
if submit_output.status == "Accepted" {
|
||||
println!("Notarizing {}", log_message);
|
||||
staple_app(app_bundle_path.to_path_buf())?;
|
||||
Ok(())
|
||||
} else if let Ok(output) = Command::new("xcrun")
|
||||
.args(["notarytool", "log"])
|
||||
.arg(&submit_output.id)
|
||||
.notarytool_args(auth, tmp_dir.path())?
|
||||
.output()
|
||||
{
|
||||
Err(anyhow::anyhow!(
|
||||
"{log_message}\nLog:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
))
|
||||
} else {
|
||||
Err(anyhow::anyhow!("{log_message}"))
|
||||
}
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"failed to parse notarytool output as JSON: `{output_str}`"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn staple_app(mut app_bundle_path: PathBuf) -> Result<()> {
|
||||
let app_bundle_path_clone = app_bundle_path.clone();
|
||||
let filename = app_bundle_path_clone
|
||||
.file_name()
|
||||
.expect("failed to get bundle filename")
|
||||
.to_str()
|
||||
.expect("failed to convert bundle filename to string");
|
||||
|
||||
app_bundle_path.pop();
|
||||
|
||||
Command::new("xcrun")
|
||||
.args(vec!["stapler", "staple", "-v", filename])
|
||||
.current_dir(app_bundle_path)
|
||||
.output()
|
||||
.context("failed to staple app.")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub trait NotarytoolCmdExt {
|
||||
fn notarytool_args(
|
||||
&mut self,
|
||||
auth: &AppleNotarizationCredentials,
|
||||
temp_dir: &Path,
|
||||
) -> Result<&mut Self>;
|
||||
}
|
||||
|
||||
impl NotarytoolCmdExt for Command {
|
||||
fn notarytool_args(
|
||||
&mut self,
|
||||
auth: &AppleNotarizationCredentials,
|
||||
temp_dir: &Path,
|
||||
) -> Result<&mut Self> {
|
||||
match auth {
|
||||
AppleNotarizationCredentials::AppleId {
|
||||
apple_id,
|
||||
password,
|
||||
team_id,
|
||||
} => Ok(
|
||||
self
|
||||
.arg("--apple-id")
|
||||
.arg(apple_id)
|
||||
.arg("--password")
|
||||
.arg(password)
|
||||
.arg("--team-id")
|
||||
.arg(team_id),
|
||||
),
|
||||
AppleNotarizationCredentials::ApiKey {
|
||||
key,
|
||||
key_id,
|
||||
issuer,
|
||||
} => {
|
||||
let key_path = match key {
|
||||
ApiKey::Raw(k) => {
|
||||
let key_path = temp_dir.join("AuthKey.p8");
|
||||
std::fs::write(&key_path, k)?;
|
||||
key_path
|
||||
}
|
||||
ApiKey::Path(p) => p.to_owned(),
|
||||
};
|
||||
|
||||
Ok(
|
||||
self
|
||||
.arg("--key-id")
|
||||
.arg(key_id)
|
||||
.arg("--key")
|
||||
.arg(key_path)
|
||||
.arg("--issuer")
|
||||
.arg(issuer),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_base64(base64: &OsStr, out_path: &Path) -> Result<()> {
|
||||
let tmp_dir = tempfile::tempdir()?;
|
||||
|
||||
let src_path = tmp_dir.path().join("src");
|
||||
let base64 = base64
|
||||
.to_str()
|
||||
.expect("failed to convert base64 to string")
|
||||
.as_bytes();
|
||||
|
||||
// as base64 contain whitespace decoding may be broken
|
||||
// https://github.com/marshallpierce/rust-base64/issues/105
|
||||
// we'll use builtin base64 command from the OS
|
||||
std::fs::write(&src_path, base64)?;
|
||||
|
||||
assert_command(
|
||||
std::process::Command::new("base64")
|
||||
.arg("--decode")
|
||||
.arg("-i")
|
||||
.arg(&src_path)
|
||||
.arg("-o")
|
||||
.arg(out_path)
|
||||
.status(),
|
||||
"failed to decode certificate",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn assert_command(
|
||||
response: Result<std::process::ExitStatus, std::io::Error>,
|
||||
error_message: &str,
|
||||
) -> std::io::Result<()> {
|
||||
let status =
|
||||
response.map_err(|e| std::io::Error::new(e.kind(), format!("{error_message}: {e}")))?;
|
||||
if !status.success() {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
error_message,
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
52
tooling/macos-sign/src/provisioning_profile.rs
Normal file
52
tooling/macos-sign/src/provisioning_profile.rs
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{ffi::OsStr, path::PathBuf, process::Command};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
|
||||
pub struct ProvisioningProfile {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl ProvisioningProfile {
|
||||
pub fn from_base64(base64: &OsStr) -> Result<Self> {
|
||||
let home_dir = dirs_next::home_dir().unwrap();
|
||||
let provisioning_profiles_folder = home_dir
|
||||
.join("Library")
|
||||
.join("MobileDevice")
|
||||
.join("Provisioning Profiles");
|
||||
std::fs::create_dir_all(&provisioning_profiles_folder).unwrap();
|
||||
|
||||
let provisioning_profile_path = provisioning_profiles_folder.join(format!(
|
||||
"{}.mobileprovision",
|
||||
Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
|
||||
));
|
||||
super::decode_base64(base64, &provisioning_profile_path)?;
|
||||
|
||||
Ok(Self {
|
||||
path: provisioning_profile_path,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn uuid(&self) -> Result<String> {
|
||||
let output = Command::new("security")
|
||||
.args(["cms", "-D", "-i"])
|
||||
.arg(&self.path)
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("failed to decode provisioning profile"));
|
||||
}
|
||||
|
||||
let plist = plist::from_bytes::<plist::Dictionary>(&output.stdout)
|
||||
.context("failed to decode provisioning profile as plist")?;
|
||||
|
||||
plist
|
||||
.get("UUID")
|
||||
.and_then(|v| v.as_string().map(ToString::to_string))
|
||||
.ok_or_else(|| anyhow::anyhow!("could not find provisioning profile UUID"))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user