fix: codesign doesn't sign frameworks or sidecar, closes #7690 (#7774)

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
Trey Smith 2023-09-15 11:09:10 -04:00 committed by GitHub
parent 2f8881c010
commit dcdbe3eb6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 322 additions and 54 deletions

View File

@ -0,0 +1,5 @@
---
"tauri-bundler": patch:bug
---
Remove extended attributes on the macOS app bundle using `xattr -cr $PATH`.

View File

@ -0,0 +1,5 @@
---
"tauri-bundler": patch:bug
---
Code sign sidecars and frameworks on macOS.

View File

@ -1 +0,0 @@
Versions/Current/Headers

View File

@ -0,0 +1 @@
Versions/Current/Headers

View File

@ -0,0 +1 @@
Versions/Current/Modules

View File

@ -1 +0,0 @@
Versions/Current/Resources

View File

@ -0,0 +1 @@
Versions/Current/Resources

View File

@ -1 +1,18 @@
// Testing that a header can be included
//
// test.h
// test
//
// Created by Trey Smith on 9/15/23.
//
#import <Foundation/Foundation.h>
//! Project version number for test.
FOUNDATION_EXPORT double testVersionNumber;
//! Project version string for test.
FOUNDATION_EXPORT const unsigned char testVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <test/PublicHeader.h>

View File

@ -0,0 +1,6 @@
framework module test {
umbrella header "test.h"
export *
module * { export * }
}

View File

@ -2,12 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>22D68</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>test</string>
<key>CFBundleIdentifier</key>
<string>com.tauri.test.framework</string>
<string>com.tauri.test</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@ -15,18 +17,30 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>NSPrincipalClass</key>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string></string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>13.3</string>
<key>DTSDKBuild</key>
<string>22E245</string>
<key>DTSDKName</key>
<string>macosx13.3</string>
<key>DTXcode</key>
<string>1431</string>
<key>DTXcodeBuild</key>
<string>14E300c</string>
<key>LSMinimumSystemVersion</key>
<string>13.2</string>
</dict>
</plist>

View File

@ -1 +0,0 @@
Test that a LICENSE file can be included

View File

@ -0,0 +1,142 @@
<?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>files</key>
<dict>
<key>Resources/Info.plist</key>
<data>
/aPV7Q20g0elr7OiZJoUNggTOcg=
</data>
</dict>
<key>files2</key>
<dict>
<key>Headers/test.h</key>
<dict>
<key>hash2</key>
<data>
5RA6Mnq5sNoaC4wKcFe6zymVmEL5Vb44G4BGqFjgZMM=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash2</key>
<data>
C6uLLSnQu9M2qLElVCkeo2JpnvWMxtArinQzmlh3v2A=
</data>
</dict>
<key>Resources/Info.plist</key>
<dict>
<key>hash2</key>
<data>
nPMotNIMgvMfHtkRdpeehzfBiCZLnksfiD3nldUPzTE=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^Resources/</key>
<true/>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^.*</key>
<true/>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

Binary file not shown.

View File

@ -0,0 +1 @@
A

View File

@ -1 +0,0 @@
Versions/Current

View File

@ -318,12 +318,12 @@ fn update_app() {
.first()
.unwrap()
.1
.join("Contents/Frameworks/test.framework/test"),
.join("Contents/Frameworks/test.framework/Modules"),
)
.expect("test.framework/test metadata");
.expect("test.framework/Modules metadata");
assert!(
meta.file_type().is_symlink(),
"test.framework/test should be a symlink"
"test.framework/Modules should be a symlink"
);
}

View File

@ -23,9 +23,9 @@
// files into the `Contents` directory of the bundle.
use super::{
super::common,
super::common::{self, CommandExt},
icon::create_icns_file,
sign::{notarize, notarize_auth, sign},
sign::{notarize, notarize_auth, sign, SignTarget},
};
use crate::Settings;
@ -33,8 +33,10 @@ use anyhow::Context;
use log::{info, warn};
use std::{
ffi::OsStr,
fs,
path::{Path, PathBuf},
process::Command,
};
/// Bundles the project.
@ -65,6 +67,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
let resources_dir = bundle_directory.join("Resources");
let bin_dir = bundle_directory.join("MacOS");
let mut sign_paths = Vec::new();
let bundle_icon_file: Option<PathBuf> =
{ create_icns_file(&resources_dir, settings).with_context(|| "Failed to create app icon")? };
@ -72,20 +75,52 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
create_info_plist(&bundle_directory, bundle_icon_file, settings)
.with_context(|| "Failed to create Info.plist")?;
copy_frameworks_to_bundle(&bundle_directory, settings)
let framework_paths = copy_frameworks_to_bundle(&bundle_directory, settings)
.with_context(|| "Failed to bundle frameworks")?;
sign_paths.extend(
framework_paths
.into_iter()
.filter(|p| {
let ext = p.extension();
ext == Some(OsStr::new("framework")) || ext == Some(OsStr::new("dylib"))
})
.map(|path| SignTarget {
path,
is_an_executable: false,
}),
);
settings.copy_resources(&resources_dir)?;
settings
let bin_paths = settings
.copy_binaries(&bin_dir)
.with_context(|| "Failed to copy external binaries")?;
sign_paths.extend(bin_paths.into_iter().map(|path| SignTarget {
path,
is_an_executable: true,
}));
copy_binaries_to_bundle(&bundle_directory, settings)?;
let bin_paths = copy_binaries_to_bundle(&bundle_directory, settings)?;
sign_paths.extend(bin_paths.into_iter().map(|path| SignTarget {
path,
is_an_executable: true,
}));
if let Some(identity) = &settings.macos().signing_identity {
// Sign frameworks and sidecar binaries first, per apple, signing must be done inside out
// https://developer.apple.com/forums/thread/701514
sign_paths.push(SignTarget {
path: app_bundle_path.clone(),
is_an_executable: true,
});
// Remove extra attributes, which could cause codesign to fail
// https://developer.apple.com/library/archive/qa/qa1940/_index.html
remove_extra_attr(&app_bundle_path)?;
// sign application
sign(app_bundle_path.clone(), identity, settings, true)?;
sign(sign_paths, identity, settings)?;
// notarization is required for distribution
match notarize_auth() {
Ok(auth) => {
@ -100,15 +135,30 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
Ok(vec![app_bundle_path])
}
fn remove_extra_attr(app_bundle_path: &Path) -> crate::Result<()> {
Command::new("xattr")
.arg("-cr")
.arg(app_bundle_path)
.output_ok()
.context("failed to remove extra attributes from app bundle")?;
Ok(())
}
// Copies the app's binaries to the bundle.
fn copy_binaries_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> {
fn copy_binaries_to_bundle(
bundle_directory: &Path,
settings: &Settings,
) -> crate::Result<Vec<PathBuf>> {
let mut paths = Vec::new();
let dest_dir = bundle_directory.join("MacOS");
for bin in settings.binaries() {
let bin_path = settings.binary_path(bin);
common::copy_file(&bin_path, &dest_dir.join(bin.name()))
let dest_path = dest_dir.join(bin.name());
common::copy_file(&bin_path, &dest_path)
.with_context(|| format!("Failed to copy binary from {:?}", bin_path))?;
paths.push(dest_path);
}
Ok(())
Ok(paths)
}
// Creates the Info.plist file.
@ -208,7 +258,12 @@ fn copy_framework_from(dest_dir: &Path, framework: &str, src_dir: &Path) -> crat
}
// Copies the macOS application bundle frameworks to the .app
fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> {
fn copy_frameworks_to_bundle(
bundle_directory: &Path,
settings: &Settings,
) -> crate::Result<Vec<PathBuf>> {
let mut paths = Vec::new();
let frameworks = settings
.macos()
.frameworks
@ -216,7 +271,7 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr
.cloned()
.unwrap_or_default();
if frameworks.is_empty() {
return Ok(());
return Ok(paths);
}
let dest_dir = bundle_directory.join("Frameworks");
fs::create_dir_all(bundle_directory)
@ -227,7 +282,9 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr
let src_name = src_path
.file_name()
.expect("Couldn't get framework filename");
common::copy_dir(&src_path, &dest_dir.join(src_name))?;
let dest_path = dest_dir.join(src_name);
common::copy_dir(&src_path, &dest_path)?;
paths.push(dest_path);
continue;
} else if framework.ends_with(".dylib") {
let src_path = PathBuf::from(framework);
@ -238,7 +295,9 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr
)));
}
let src_name = src_path.file_name().expect("Couldn't get library filename");
common::copy_file(&src_path, &dest_dir.join(src_name))?;
let dest_path = dest_dir.join(src_name);
common::copy_file(&src_path, &dest_path)?;
paths.push(dest_path);
continue;
} else if framework.contains('/') {
return Err(crate::Error::GenericError(format!(
@ -265,5 +324,5 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr
framework
)));
}
Ok(())
Ok(paths)
}

View File

@ -153,7 +153,14 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
// Sign DMG if needed
if let Some(identity) = &settings.macos().signing_identity {
super::sign::sign(dmg_path.clone(), identity, settings, false)?;
super::sign::sign(
vec![super::sign::SignTarget {
path: dmg_path.clone(),
is_an_executable: false,
}],
identity,
settings,
)?;
}
Ok(Bundled {

View File

@ -144,13 +144,13 @@ pub fn delete_keychain() {
.output_ok();
}
pub fn sign(
path_to_sign: PathBuf,
identity: &str,
settings: &Settings,
is_an_executable: bool,
) -> crate::Result<()> {
info!(action = "Signing"; "{} with identity \"{}\"", path_to_sign.display(), identity);
pub struct SignTarget {
pub path: PathBuf,
pub is_an_executable: bool,
}
pub fn sign(targets: Vec<SignTarget>, identity: &str, settings: &Settings) -> crate::Result<()> {
info!(action = "Signing"; "with identity \"{}\"", identity);
let setup_keychain = if let (Some(certificate_encoded), Some(certificate_password)) = (
var_os("APPLE_CERTIFICATE"),
@ -164,20 +164,24 @@ pub fn sign(
false
};
let res = try_sign(
path_to_sign,
identity,
settings,
is_an_executable,
setup_keychain,
);
info!("Signing app bundle...");
for target in targets {
try_sign(
target.path,
identity,
settings,
target.is_an_executable,
setup_keychain,
)?;
}
if setup_keychain {
// delete the keychain again after signing
delete_keychain();
}
res
Ok(())
}
fn try_sign(
@ -187,6 +191,8 @@ fn try_sign(
is_an_executable: bool,
tauri_keychain: bool,
) -> crate::Result<()> {
info!(action = "Signing"; "{}", path_to_sign.display());
let mut args = vec!["--force", "-s", identity];
if tauri_keychain {
@ -205,13 +211,9 @@ fn try_sign(
args.push("runtime");
}
if path_to_sign.is_dir() {
args.push("--deep");
}
Command::new("codesign")
.args(args)
.arg(path_to_sign.to_string_lossy().to_string())
.arg(path_to_sign)
.output_ok()
.context("failed to sign app")?;
@ -260,7 +262,14 @@ pub fn notarize(
// sign the zip file
if let Some(identity) = &settings.macos().signing_identity {
sign(zip_path.clone(), identity, settings, false)?;
sign(
vec![SignTarget {
path: zip_path.clone(),
is_an_executable: false,
}],
identity,
settings,
)?;
};
let notarize_args = vec![

View File

@ -761,7 +761,11 @@ impl Settings {
}
/// Copies external binaries to a path.
pub fn copy_binaries(&self, path: &Path) -> crate::Result<()> {
///
/// Returns the list of destination paths.
pub fn copy_binaries(&self, path: &Path) -> crate::Result<Vec<PathBuf>> {
let mut paths = Vec::new();
for src in self.external_binaries() {
let src = src?;
let dest = path.join(
@ -771,9 +775,10 @@ impl Settings {
.to_string_lossy()
.replace(&format!("-{}", self.target), ""),
);
common::copy_file(&src, dest)?;
common::copy_file(&src, &dest)?;
paths.push(dest);
}
Ok(())
Ok(paths)
}
/// Copies resources to a path.