Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Codesign fixes and notarization support #307

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
fvarrui merged 2 commits into javapackager:pr-307 from maths22:codesign-fixes
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/macosx-specific-properties.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<entitlements>path/to/entitlements.plist</entitlements>
<codesignApp>true|false</codesignApp>
<hardenedCodesign>true|false</hardenedCodesign>
<notarizeApp>true|false</notarizeApp>
<keyChainProfile>xcrun_notarytool_profile_name</keyChainProfile>

<!-- properties used for DMG disk image generation -->
<backgroundImage>path/to/png</backgroundImage>
Expand Down Expand Up @@ -62,11 +64,13 @@
## Signing properties

| Property | Mandatory | Default value | Description |
|------------------| --------- |------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------|
|--------------------| --------- |---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| `developerId` | :x: | | Signing identity. |
| `entitlements` | :x: | | Path to [entitlements](https://developer.apple.com/documentation/bundleresources/entitlements) file. |
| `codesignApp` | :x: | `true` | If it is set to `false`, generated app will not be codesigned. |
| `hardenedCodesign` | :x: | `true` | If it is set to `true`, enable [hardened runtime](https://developer.apple.com/documentation/security/hardened_runtime) if MacOS version >= 10.13.6. |
| `notarizeApp` | :x: | `false` | If it is set to `true`, generated app will be submitted to apple for notarization and the ticket will be stapled. |
| `keyChainProfile` | :x: | | Profile name originally provided to `xcrun notarytool store-credentials`. Must be set if `notarizeApp` is `true`.
| `macStartup` | :x: | `SCRIPT` | App startup type, using a `SCRIPT` or a binary (compiled version of the script: `UNIVERSAL`, `X86_64` or `ARM64`). |

## DMG generation properties
Expand Down
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.github.fvarrui.javapackager.gradle;

import io.github.fvarrui.javapackager.model.LinuxConfig;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public abstract class LinuxTaskConfig {
@Input
@Optional
public abstract ListProperty<String> getCategories();
@Input
@Optional
public abstract Property<Boolean> isGenerateDeb();
@Input
@Optional
public abstract Property<Boolean> isGenerateRpm();
@Input
@Optional
public abstract Property<Boolean> isGenerateAppImage();;
@Input
@Optional
public abstract RegularFileProperty getPngFile();
@Input
@Optional
public abstract Property<Boolean> isWrapJar();

public LinuxConfig buildConfig() {
LinuxConfig ret = new LinuxConfig();
ret.setCategories(getCategories().getOrElse(new ArrayList<>()));
ret.setGenerateDeb(isGenerateDeb().getOrElse(true));
ret.setGenerateRpm(isGenerateRpm().getOrElse(true));
ret.setGenerateAppImage(isGenerateAppImage().getOrElse(true));
ret.setPngFile(getPngFile().getAsFile().getOrNull());
ret.setWrapJar(isWrapJar().getOrElse(true));
return ret;
}
}
34 changes: 31 additions & 3 deletions src/main/java/io/github/fvarrui/javapackager/model/MacConfig.java
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ public class MacConfig implements Serializable {
private File provisionProfile;
private File customLauncher;
private File customInfoPlist;
private File customRuntimeInfoPlist;
private boolean codesignApp = true;
private boolean notarizeApp = false;
private String keyChainProfile;
private InfoPlist infoPlist = new InfoPlist();
private boolean hardenedCodesign = true;
private MacStartup macStartup = MacStartup.SCRIPT;
Expand Down Expand Up @@ -209,6 +212,14 @@ public void setCustomInfoPlist(File customInfoPlist) {
this.customInfoPlist = customInfoPlist;
}

public File getCustomRuntimeInfoPlist() {
return customRuntimeInfoPlist;
}

public void setCustomRuntimeInfoPlist(File customRuntimeInfoPlist) {
this.customRuntimeInfoPlist = customRuntimeInfoPlist;
}

public File getProvisionProfile() {
return provisionProfile;
}
Expand All @@ -233,6 +244,22 @@ public void setCodesignApp(boolean codesignApp) {
this.codesignApp = codesignApp;
}

public boolean isNotarizeApp() {
return notarizeApp;
}

public void setNotarizeApp(boolean notarizeApp) {
this.notarizeApp = notarizeApp;
}

public String getKeyChainProfile() {
return keyChainProfile;
}

public void setKeyChainProfile(String keyChainProfile) {
this.keyChainProfile = keyChainProfile;
}

public InfoPlist getInfoPlist() {
return infoPlist;
}
Expand Down Expand Up @@ -266,9 +293,10 @@ public String toString() {
+ ", volumeName=" + volumeName + ", generateDmg=" + generateDmg + ", generatePkg=" + generatePkg
+ ", relocateJar=" + relocateJar + ", appId=" + appId + ", developerId=" + developerId
+ ", entitlements=" + entitlements + ", provisionProfile=" + provisionProfile + ", customLauncher="
+ customLauncher + ", customInfoPlist=" + customInfoPlist + ", codesignApp=" + codesignApp
+ ", infoPlist=" + infoPlist + ", hardenedCodesign=" + hardenedCodesign + ", macStartup=" + macStartup
+ "]";
+ customLauncher + ", customInfoPlist=" + customInfoPlist + ", customRuntimeInfoPlist="
+ customRuntimeInfoPlist + ", codesignApp=" + codesignApp + ", notarizeApp=" + notarizeApp
+ ", keyChainProfile=" + keyChainProfile + ", infoPlist=" + infoPlist + ", hardenedCodesign="
+ hardenedCodesign + ", macStartup=" + macStartup + "]";
}

/**
Expand Down
147 changes: 120 additions & 27 deletions src/main/java/io/github/fvarrui/javapackager/packagers/MacPackager.java
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,19 @@

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
* Packager for MacOS
Expand All @@ -25,6 +33,7 @@ public class MacPackager extends Packager {
private File resourcesFolder;
private File javaFolder;
private File macOSFolder;
private File jreBundleFolder;

public File getAppFile() {
return appFile;
Expand Down Expand Up @@ -73,7 +82,8 @@ protected void doCreateAppStructure() throws Exception {
// sets common folders
this.executableDestinationFolder = macOSFolder;
this.jarFileDestinationFolder = javaFolder;
this.jreDestinationFolder = new File(contentsFolder, "PlugIns/" + jreDirectoryName + "/Contents/Home");
this.jreBundleFolder = new File(contentsFolder, "PlugIns/" + jreDirectoryName + ".jre");
this.jreDestinationFolder = new File(jreBundleFolder, "Contents/Home");
this.resourcesDestinationFolder = resourcesFolder;

}
Expand All @@ -83,6 +93,9 @@ protected void doCreateAppStructure() throws Exception {
*/
@Override
public File doCreateApp() throws Exception {
if(bundleJre) {
processRuntimeInfoPlistFile();
}

// copies jarfile to Java folder
FileUtils.copyFileToFolder(jarFile, javaFolder);
Expand All @@ -97,6 +110,8 @@ public File doCreateApp() throws Exception {

codesign();

notarize();

return appFile;
}

Expand Down Expand Up @@ -157,6 +172,21 @@ private void processInfoPlistFile() throws Exception {
Logger.info("Info.plist file created in " + infoPlistFile.getAbsolutePath());
}

/**
* Creates and writes the Info.plist inside the JRE if no custom file is specified.
* @throws Exception if anything goes wrong
*/
private void processRuntimeInfoPlistFile() throws Exception {
File infoPlistFile = new File(jreBundleFolder, "Contents/Info.plist");
if(macConfig.getCustomRuntimeInfoPlist() != null && macConfig.getCustomRuntimeInfoPlist().isFile() && macConfig.getCustomRuntimeInfoPlist().canRead()){
FileUtils.copyFileToFile(macConfig.getCustomRuntimeInfoPlist(), infoPlistFile);
} else {
VelocityUtils.render("mac/RuntimeInfo.plist.vtl", infoPlistFile, this);
XMLUtils.prettify(infoPlistFile);
}
Logger.info("RuntimeInfo.plist file created in " + infoPlistFile.getAbsolutePath());
}

private void codesign() throws Exception {
if (!Platform.mac.isCurrentPlatform()) {
Logger.warn("Generated app could not be signed due to current platform is " + Platform.getCurrentPlatform());
Expand All @@ -167,6 +197,18 @@ private void codesign() throws Exception {
}
}

private void notarize() throws Exception {
if (!Platform.mac.isCurrentPlatform()) {
Logger.warn("Generated app could not be notarized due to current platform is " + Platform.getCurrentPlatform());
} else if (!getMacConfig().isCodesignApp()) {
Logger.warn("App codesigning disabled. Cannot notarize unsigned app");
} else if (!getMacConfig().isNotarizeApp()) {
Logger.warn("App notarization disabled");
} else {
notarize(this.macConfig.getKeyChainProfile(), this.appFile);
}
}

private void processProvisionProfileFile() throws Exception {
if (macConfig.getProvisionProfile() != null && macConfig.getProvisionProfile().isFile() && macConfig.getProvisionProfile().canRead()) {
// file name must be 'embedded.provisionprofile'
Expand Down Expand Up @@ -195,13 +237,13 @@ private File preparePrecompiledStartupStub() throws Exception {

private void codesign(String developerId, File entitlements, File appFile) throws Exception {

prepareEntitlementFile(entitlements);
entitlements = prepareEntitlementFile(entitlements);

manualDeepSign(appFile, developerId, entitlements);
signAppBundle(appFile, developerId, entitlements);

}

private void prepareEntitlementFile(File entitlements) throws Exception {
private File prepareEntitlementFile(File entitlements) throws Exception {
// if entitlements.plist file not specified, use a default one
if (entitlements == null) {
Logger.warn("Entitlements file not specified. Using defaults!");
Expand All @@ -210,45 +252,56 @@ private void prepareEntitlementFile(File entitlements) throws Exception {
} else if (!entitlements.exists()) {
throw new Exception("Entitlements file doesn't exist: " + entitlements);
}
return entitlements;
}

private void manualDeepSign(File appFolder, String developerCertificateName, File entitlements) throws IOException, CommandLineException {

// codesign each file in app
List<Object> findCommandArgs = new ArrayList<>();
findCommandArgs.add(appFolder);
findCommandArgs.add("-depth"); // execute 'codesign' in 'reverse order', i.e., deepest files first
findCommandArgs.add("-type");
findCommandArgs.add("f"); // filter for files only
findCommandArgs.add("-exec");
findCommandArgs.add("codesign");
findCommandArgs.add("-f");
addHardenedCodesign(findCommandArgs);
findCommandArgs.add("-s");
findCommandArgs.add(developerCertificateName);
findCommandArgs.add("--entitlements");
findCommandArgs.add(entitlements);
findCommandArgs.add("{}");
findCommandArgs.add("\\;");
CommandUtils.execute("find", findCommandArgs);
private void signAppBundle(File appFolder, String developerCertificateName, File entitlements) throws IOException, CommandLineException {
// Sign all embedded executables and dynamic libraries
// Structure and order adapted from the JRE's jpackage
try (Stream<Path> stream = Files.walk(appFolder.toPath())) {
stream.filter(p -> Files.isRegularFile(p)
&& (Files.isExecutable(p) || p.toString().endsWith(".dylib"))
&& !(p.toString().contains("dylib.dSYM/Contents"))
&& !(p.equals(this.executable.toPath()))
).forEach(p -> {
if (Files.isSymbolicLink(p)) {
Logger.debug("Skipping signing symlink: " + p);
} else {
try {
codesign(Files.isExecutable(p) ? entitlements : null, developerCertificateName, p.toFile());
} catch (IOException | CommandLineException e) {
throw new RuntimeException(e);
}
}
});
}

// sign the JRE itself after signing all its contents
codesign(developerCertificateName, jreBundleFolder);

// make sure the executable is signed last
codesign(entitlements, developerCertificateName, this.executable);

// finally, sign the top level directory
codesign(entitlements, developerCertificateName, appFolder);
}

private void codesign(String developerCertificateName, File file) throws IOException, CommandLineException {
codesign(null, developerCertificateName, file);
}

private void codesign(File entitlements, String developerCertificateName, File file) throws IOException, CommandLineException {
List<Object> arguments = new ArrayList<>();
arguments.add("-f");
addHardenedCodesign(arguments);
arguments.add("--entitlements");
arguments.add(entitlements);
if(entitlements != null) {
addHardenedCodesign(arguments);
arguments.add("--entitlements");
arguments.add(entitlements);
}
arguments.add("--timestamp");
arguments.add("-s");
arguments.add(developerCertificateName);
arguments.add(appFolder);
arguments.add(file);
CommandUtils.execute("codesign", arguments);
}

Expand All @@ -263,4 +316,44 @@ private void addHardenedCodesign(Collection<Object> args){
}
}

private void notarize(String keyChainProfile, File appFile) throws IOException, CommandLineException {
Path zippedApp = null;
try {
zippedApp = zipApp(appFile);
List<Object> notarizeArgs = new ArrayList<>();
notarizeArgs.add("notarytool");
notarizeArgs.add("submit");
notarizeArgs.add(zippedApp.toString());
notarizeArgs.add("--wait");
notarizeArgs.add("--keychain-profile");
notarizeArgs.add(keyChainProfile);
CommandUtils.execute("xcrun", notarizeArgs);
} finally {
if(zippedApp != null) {
Files.deleteIfExists(zippedApp);
}
}

List<Object> stapleArgs = new ArrayList<>();
stapleArgs.add("stapler");
stapleArgs.add("staple");
stapleArgs.add(appFile);
CommandUtils.execute("xcrun", stapleArgs);
}

private Path zipApp(File appFile) throws IOException {
Path zipPath = assetsFolder.toPath().resolve(appFile.getName() + "-notarization.zip");
try(ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipPath))) {
Path sourcePath = appFile.toPath();
Files.walkFileTree(sourcePath, new SimpleFileVisitor<Path>() {
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
zos.putNextEntry(new ZipEntry(sourcePath.getParent().relativize(file).toString()));
Files.copy(file, zos);
zos.closeEntry();
return FileVisitResult.CONTINUE;
}
});
}
return zipPath;
}
}
2 changes: 1 addition & 1 deletion src/main/resources/mac/Info.plist.vtl
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
<dict>
#if ($info.bundleJre)
<key>JAVA_HOME</key>
<string>Contents/PlugIns/${info.jreDirectoryName}/Contents/Home</string>
<string>Contents/PlugIns/${info.jreDirectoryName}.jre/Contents/Home</string>
#end
#if($info.envPath)
<key>PATH</key>
Expand Down
Loading

AltStyle によって変換されたページ (->オリジナル) /