8289030: [macos] app image signature invalid when creating DMG or PKG

Reviewed-by: asemenyuk
This commit is contained in:
Alexander Matveev 2022-07-08 00:17:11 +00:00
parent 1304390b3e
commit 64286074ba
14 changed files with 215 additions and 27 deletions

View File

@ -38,7 +38,9 @@ import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE;
public class MacAppBundler extends AppImageBundler {
public MacAppBundler() {
setAppImageSupplier(MacAppImageBuilder::new);
setAppImageSupplier(imageOutDir -> {
return new MacAppImageBuilder(imageOutDir, isDependentTask());
});
setParamsValidator(MacAppBundler::doValidate);
}

View File

@ -90,6 +90,8 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
private final Path runtimeDir;
private final Path runtimeRoot;
private final boolean withPackageFile;
private static List<String> keyChains;
public static final BundlerParamInfo<Boolean>
@ -243,10 +245,11 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
(s, p) -> Arrays.asList(s.split("(,|\\s)+"))
);
public MacAppImageBuilder(Path imageOutDir) {
public MacAppImageBuilder(Path imageOutDir, boolean withPackageFile) {
super(imageOutDir);
this.root = imageOutDir;
this.withPackageFile = withPackageFile;
this.contentsDir = root.resolve("Contents");
this.resourcesDir = appLayout.destktopIntegrationDirectory();
this.macOSDir = appLayout.launchersDirectory();
@ -309,6 +312,11 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
// Copy class path entries to Java folder
copyApplication(params);
if (withPackageFile) {
new PackageFile(APP_NAME.fetchFrom(params)).save(
ApplicationLayout.macAppImage().resolveAt(root));
}
/*********** Take care of "config" files *******/
createResource(TEMPLATE_BUNDLE_ICON, params)

View File

@ -29,6 +29,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
@ -113,7 +114,8 @@ public abstract class MacBaseInstallerBundler extends AbstractBundler {
}
public MacBaseInstallerBundler() {
appImageBundler = new MacAppBundler().setDependentTask(true);
appImageBundler = new MacAppBundler()
.setDependentTask(true);
}
protected void validateAppImageAndBundeler(
@ -136,11 +138,18 @@ public abstract class MacBaseInstallerBundler extends AbstractBundler {
I18N.getString(
"message.app-image-requires-app-name.advice"));
}
if (Optional.ofNullable(
SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
// if signing bundle with app-image, warn user if app-image
// is not already signed.
if (!(AppImageFile.load(applicationImage).isSigned())) {
if (AppImageFile.load(applicationImage).isSigned()) {
if (!Files.exists(
PackageFile.getPathInAppImage(applicationImage))) {
Log.info(MessageFormat.format(I18N.getString(
"warning.per.user.app.image.signed"),
PackageFile.getPathInAppImage(applicationImage)));
}
} else {
if (Optional.ofNullable(
SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
// if signing bundle with app-image, warn user if app-image
// is not already signed.
Log.info(MessageFormat.format(I18N.getString(
"warning.unsigned.app.image"), getID()));
}
@ -158,17 +167,19 @@ public abstract class MacBaseInstallerBundler extends AbstractBundler {
StandardBundlerParam.getPredefinedAppImage(params);
if (predefinedImage != null) {
appDir = appImageRoot.resolve(APP_NAME.fetchFrom(params) + ".app");
IOUtils.copyRecursive(predefinedImage, appDir);
IOUtils.copyRecursive(predefinedImage, appDir,
LinkOption.NOFOLLOW_LINKS);
// Create PackageFile if predefined app image is not signed
if (!StandardBundlerParam.isRuntimeInstaller(params) &&
!AppImageFile.load(predefinedImage).isSigned()) {
new PackageFile(APP_NAME.fetchFrom(params)).save(
ApplicationLayout.macAppImage().resolveAt(appDir));
}
} else {
appDir = appImageBundler.execute(params, appImageRoot);
}
if (!StandardBundlerParam.isRuntimeInstaller(params)) {
new PackageFile(APP_NAME.fetchFrom(params)).save(
ApplicationLayout.macAppImage().resolveAt(appDir));
Files.deleteIfExists(AppImageFile.getPathInAppImage(appDir));
}
return appDir;
}

View File

@ -94,3 +94,4 @@ message.signing.pkg=Warning: For signing PKG, you might need to set "Always Trus
message.setfile.dmg=Setting custom icon on DMG file skipped because 'SetFile' utility was not found. Installing Xcode with Command Line Tools should resolve this issue.
message.install-dir-ignored=Warning: "--install-dir" is not supported by DMG and will be default to /Applications.
warning.unsigned.app.image=Warning: Using unsigned app-image to build signed {0}.
warning.per.user.app.image.signed=Warning: Support for per-user configuration of the installed application will not be supported due to missing "{0}" in predefined signed application image.

View File

@ -90,3 +90,4 @@ message.signing.pkg=Warnung: Zum Signieren von PKG m\u00FCssen Sie m\u00F6gliche
message.setfile.dmg=Das Festlegen des benutzerdefinierten Symbols f\u00FCr die DMG-Datei wurde \u00FCbersprungen, weil das Utility "SetFile" nicht gefunden wurde. Durch Installieren von Xcode mit Befehlszeilentools sollte dieses Problem behoben werden.
message.install-dir-ignored=Warnung: "--install-dir" wird von DMG nicht unterst\u00FCtzt. Stattdessen wird standardm\u00E4\u00DFig /Applications verwendet.
warning.unsigned.app.image=Warnung: Nicht signiertes app-image wird zum Erstellen von signiertem {0} verwendet.
warning.per.user.app.image.signed=Warning: Support for per-user configuration of the installed application will not be supported due to missing "{0}" in predefined signed application image.

View File

@ -94,3 +94,4 @@ message.signing.pkg=\u8B66\u544A: PKG\u3078\u306E\u7F72\u540D\u306E\u5834\u5408\
message.setfile.dmg='SetFile'\u30E6\u30FC\u30C6\u30A3\u30EA\u30C6\u30A3\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u305F\u3081\u3001DMG\u30D5\u30A1\u30A4\u30EB\u3067\u306E\u30AB\u30B9\u30BF\u30E0\u30FB\u30A2\u30A4\u30B3\u30F3\u306E\u8A2D\u5B9A\u304C\u30B9\u30AD\u30C3\u30D7\u3055\u308C\u307E\u3057\u305F\u3002Xcode\u3068\u30B3\u30DE\u30F3\u30C9\u30FB\u30E9\u30A4\u30F3\u30FB\u30C4\u30FC\u30EB\u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3059\u308B\u3068\u3001\u3053\u306E\u554F\u984C\u306F\u89E3\u6C7A\u3055\u308C\u307E\u3059\u3002
message.install-dir-ignored=\u8B66\u544A: "--install-dir"\u306FDMG\u3067\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002/Applications\u306B\u30C7\u30D5\u30A9\u30EB\u30C8\u8A2D\u5B9A\u3055\u308C\u307E\u3059\u3002
warning.unsigned.app.image=\u8B66\u544A: \u7F72\u540D\u3055\u308C\u3066\u3044\u306A\u3044app-image\u3092\u4F7F\u7528\u3057\u3066\u7F72\u540D\u3055\u308C\u305F{0}\u3092\u4F5C\u6210\u3057\u307E\u3059\u3002
warning.per.user.app.image.signed=Warning: Support for per-user configuration of the installed application will not be supported due to missing "{0}" in predefined signed application image.

View File

@ -94,3 +94,4 @@ message.signing.pkg=\u8B66\u544A\uFF1A\u8981\u5BF9 PKG \u8FDB\u884C\u7B7E\u540D\
message.setfile.dmg=\u7531\u4E8E\u672A\u627E\u5230 'SetFile' \u5B9E\u7528\u7A0B\u5E8F\uFF0C\u8DF3\u8FC7\u4E86\u9488\u5BF9 DMG \u6587\u4EF6\u8BBE\u7F6E\u5B9A\u5236\u56FE\u6807\u7684\u64CD\u4F5C\u3002\u5B89\u88C5\u5E26\u547D\u4EE4\u884C\u5DE5\u5177\u7684 Xcode \u5E94\u80FD\u89E3\u51B3\u6B64\u95EE\u9898\u3002
message.install-dir-ignored=\u8B66\u544A\uFF1A"--install-dir" \u4E0D\u53D7 DMG \u652F\u6301\uFF0C\u5C06\u9ED8\u8BA4\u4E3A /Applications\u3002
warning.unsigned.app.image=\u8B66\u544A\uFF1A\u4F7F\u7528\u672A\u7B7E\u540D\u7684 app-image \u751F\u6210\u5DF2\u7B7E\u540D\u7684 {0}\u3002
warning.per.user.app.image.signed=Warning: Support for per-user configuration of the installed application will not be supported due to missing "{0}" in predefined signed application image.

View File

@ -76,7 +76,9 @@ public abstract class AbstractAppImageBuilder {
IOUtils.copyRecursive(SOURCE_DIR.fetchFrom(params),
appLayout.appDirectory());
}
AppImageFile.save(root, params);
List<String> items = APP_CONTENT.fetchFrom(params);
for (String item : items) {
IOUtils.copyRecursive(Path.of(item),

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -112,6 +112,10 @@ class AppImageBundler extends AbstractBundler {
return this;
}
final boolean isDependentTask() {
return dependentTask;
}
final AppImageBundler setAppImageSupplier(
Function<Path, AbstractAppImageBuilder> v) {
appImageSupplier = v;

View File

@ -146,7 +146,7 @@ public final class AppImageFile {
return mainClass;
}
boolean isSigned() {
public boolean isSigned() {
return signed;
}
@ -218,7 +218,7 @@ public final class AppImageFile {
* @return valid info about application image or null
* @throws IOException
*/
static AppImageFile load(Path appImageDir) {
public static AppImageFile load(Path appImageDir) {
try {
Document doc = readXml(appImageDir);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -35,6 +35,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.CopyOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
@ -112,12 +113,14 @@ public class IOUtils {
}
}
public static void copyRecursive(Path src, Path dest) throws IOException {
copyRecursive(src, dest, List.of());
public static void copyRecursive(Path src, Path dest, CopyOption... options)
throws IOException {
copyRecursive(src, dest, List.of(), options);
}
public static void copyRecursive(Path src, Path dest,
final List<String> excludes) throws IOException {
final List<String> excludes, CopyOption... options)
throws IOException {
Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(final Path dir,
@ -134,7 +137,7 @@ public class IOUtils {
public FileVisitResult visitFile(final Path file,
final BasicFileAttributes attrs) throws IOException {
if (!excludes.contains(file.toFile().getName())) {
Files.copy(file, dest.resolve(src.relativize(file)));
Files.copy(file, dest.resolve(src.relativize(file)), options);
}
return FileVisitResult.CONTINUE;
}

View File

@ -820,7 +820,7 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
private void assertAppImageFile() {
final Path lookupPath = AppImageFile.getPathInAppImage(Path.of(""));
if (isRuntime() || !isImagePackageType()) {
if (isRuntime() || (!isImagePackageType() && !TKit.isOSX())) {
assertFileInAppImage(lookupPath, null);
} else {
assertFileInAppImage(lookupPath, lookupPath);
@ -833,7 +833,17 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
if (isRuntime() || isImagePackageType() || TKit.isLinux()) {
assertFileInAppImage(lookupPath, null);
} else {
assertFileInAppImage(lookupPath, lookupPath);
if (TKit.isOSX() && hasArgument("--app-image")) {
String appImage = getArgumentValue("--app-image",
() -> null);
if (AppImageFile.load(Path.of(appImage)).isSigned()) {
assertFileInAppImage(lookupPath, null);
} else {
assertFileInAppImage(lookupPath, lookupPath);
}
} else {
assertFileInAppImage(lookupPath, lookupPath);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -22,6 +22,7 @@
*/
import java.nio.file.Path;
import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.PackageType;
@ -75,7 +76,8 @@ public class SigningPackageTest {
private static void verifyAppImageInDMG(JPackageCommand cmd) {
MacHelper.withExplodedDmg(cmd, dmgImage -> {
Path launcherPath = dmgImage.resolve(Path.of("Contents", "MacOS", cmd.name()));
Path launcherPath = ApplicationLayout.platformAppImage()
.resolveAt(dmgImage).launchersDirectory().resolve(cmd.name());
// We will be called with all folders in DMG since JDK-8263155, but
// we only need to verify app.
if (dmgImage.endsWith(cmd.name() + ".app")) {

View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.MacHelper;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Annotations.Parameter;
/**
* Note: Testing unsgined app image is done to verify support for per-user
* configuration by checking for PackageFile.
* Tests generation of dmg and pkg from signed or unsigned predefined app image.
* Test will generate pkg and verifies its signature. It verifies that dmg
* is not signed, but app image inside dmg is signed or unsigned. This test
* requires that the machine is configured with test certificate for
* "Developer ID Installer: jpackage.openjdk.java.net" in
* jpackagerTest keychain with
* always allowed access to this keychain for user which runs test.
* note:
* "jpackage.openjdk.java.net" can be over-ridden by systerm property
* "jpackage.mac.signing.key.user.name", and
* "jpackagerTest" can be over-ridden by system property
* "jpackage.mac.signing.keychain"
*/
/*
* @test
* @summary jpackage with --type pkg,dmg --app-image
* @library ../helpers
* @library /test/lib
* @library base
* @key jpackagePlatformPackage
* @build SigningBase
* @build SigningCheck
* @build jtreg.SkippedException
* @build jdk.jpackage.test.*
* @build SigningPackageTwoStepTest
* @modules jdk.jpackage/jdk.jpackage.internal
* @requires (os.family == "mac")
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=SigningPackageTwoStepTest
*/
public class SigningPackageTwoStepTest {
private static void verifyPKG(JPackageCommand cmd) {
if (!cmd.hasArgument("--mac-sign")) {
return; // Nothing to check if not signed
}
Path outputBundle = cmd.outputBundle();
SigningBase.verifyPkgutil(outputBundle);
SigningBase.verifySpctl(outputBundle, "install");
}
private static void verifyDMG(JPackageCommand cmd) {
// DMG always unsigned, so we will check it
Path outputBundle = cmd.outputBundle();
SigningBase.verifyCodesign(outputBundle, false);
}
private static void verifyAppImageInDMG(JPackageCommand cmd) {
MacHelper.withExplodedDmg(cmd, dmgImage -> {
// We will be called with all folders in DMG since JDK-8263155, but
// we only need to verify app.
if (dmgImage.endsWith(cmd.name() + ".app")) {
boolean isSigned = cmd.hasArgument("--mac-sign");
Path launcherPath = ApplicationLayout.platformAppImage()
.resolveAt(dmgImage).launchersDirectory().resolve(cmd.name());
SigningBase.verifyCodesign(launcherPath, isSigned);
SigningBase.verifyCodesign(dmgImage, isSigned);
if (isSigned) {
SigningBase.verifySpctl(dmgImage, "exec");
}
}
});
}
@Test
@Parameter("true")
@Parameter("false")
public static void test(boolean signAppImage) throws Exception {
SigningCheck.checkCertificates();
Path appimageOutput = TKit.createTempDirectory("appimage");
JPackageCommand appImageCmd = JPackageCommand.helloAppImage()
.setArgumentValue("--dest", appimageOutput);
if (signAppImage) {
appImageCmd.addArguments("--mac-sign")
.addArguments("--mac-signing-key-user-name",
SigningBase.DEV_NAME)
.addArguments("--mac-signing-keychain",
SigningBase.KEYCHAIN);
}
new PackageTest()
.addRunOnceInitializer(() -> appImageCmd.execute())
.forTypes(PackageType.MAC)
.addInitializer(cmd -> {
cmd.addArguments("--app-image", appImageCmd.outputBundle());
cmd.removeArgumentWithValue("--input");
if (signAppImage) {
cmd.addArguments("--mac-sign",
"--mac-signing-key-user-name",
SigningBase.DEV_NAME,
"--mac-signing-keychain",
SigningBase.KEYCHAIN);
}
})
.forTypes(PackageType.MAC_PKG)
.addBundleVerifier(SigningPackageTwoStepTest::verifyPKG)
.forTypes(PackageType.MAC_DMG)
.addBundleVerifier(SigningPackageTwoStepTest::verifyDMG)
.addBundleVerifier(SigningPackageTwoStepTest::verifyAppImageInDMG)
.run();
}
}