8293462: [macos] app image signature invalid when creating DMG or PKG from post processed signed image

Reviewed-by: asemenyuk
This commit is contained in:
Alexander Matveev 2022-09-26 22:48:25 +00:00
parent 43eff2b309
commit 1e222bccd3
12 changed files with 403 additions and 66 deletions

View File

@ -265,9 +265,29 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
@Override @Override
public void prepareApplicationFiles(Map<String, ? super Object> params) public void prepareApplicationFiles(Map<String, ? super Object> params)
throws IOException { throws IOException {
// If predefine app image is provided, then just sign it and return. // If predefined app image is provided, then just sign it and return.
if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) { Path predefinedAppImage = PREDEFINED_APP_IMAGE.fetchFrom(params);
doSigning(params); if (predefinedAppImage != null) {
// Mark app image as signed, before we signing it.
AppImageFile appImageFile =
AppImageFile.load(predefinedAppImage);
if (!appImageFile.isSigned()) {
appImageFile.copyAsSigned().save(predefinedAppImage);
} else {
appImageFile = null;
}
try {
doSigning(params);
} catch (Exception ex) {
// Restore original app image file if signing failed
if (appImageFile != null) {
appImageFile.save(predefinedAppImage);
}
throw ex;
}
return; return;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2015, 2021, 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it

View File

@ -33,6 +33,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
@ -59,11 +61,12 @@ import static jdk.jpackage.internal.StandardBundlerParam.APP_STORE;
public final class AppImageFile { public final class AppImageFile {
// These values will be loaded from AppImage xml file. // These values will be loaded from AppImage xml file.
private final String creatorVersion; private final String appVersion;
private final String creatorPlatform;
private final String launcherName; private final String launcherName;
private final String mainClass; private final String mainClass;
private final List<LauncherInfo> addLauncherInfos; private final List<LauncherInfo> addLauncherInfos;
private final String creatorVersion;
private final String creatorPlatform;
private final boolean signed; private final boolean signed;
private final boolean appStore; private final boolean appStore;
@ -73,15 +76,13 @@ public final class AppImageFile {
Platform.LINUX, "linux", Platform.WINDOWS, "windows", Platform.MAC, Platform.LINUX, "linux", Platform.WINDOWS, "windows", Platform.MAC,
"macOS"); "macOS");
private AppImageFile(Path appImageDir, String launcherName, String mainClass, private AppImageFile(Path appImageDir, String appVersion, String launcherName,
List<LauncherInfo> launcherInfos, String creatorVersion, String mainClass, List<LauncherInfo> launcherInfos,
String creatorPlatform, String signedStr, String appStoreStr) { String creatorVersion, String creatorPlatform, String signedStr,
String appStoreStr) {
boolean isValid = true; boolean isValid = true;
if (!Objects.equals(getVersion(), creatorVersion)) {
isValid = false;
}
if (!Objects.equals(getPlatform(), creatorPlatform)) { if (appVersion == null || appVersion.length() == 0) {
isValid = false; isValid = false;
} }
@ -99,6 +100,14 @@ public final class AppImageFile {
} }
} }
if (!Objects.equals(getVersion(), creatorVersion)) {
isValid = false;
}
if (!Objects.equals(getPlatform(), creatorPlatform)) {
isValid = false;
}
if (signedStr == null || if (signedStr == null ||
!("true".equals(signedStr) || "false".equals(signedStr))) { !("true".equals(signedStr) || "false".equals(signedStr))) {
isValid = false; isValid = false;
@ -111,9 +120,11 @@ public final class AppImageFile {
if (!isValid) { if (!isValid) {
throw new RuntimeException(MessageFormat.format(I18N.getString( throw new RuntimeException(MessageFormat.format(I18N.getString(
"error.invalid-app-image"), appImageDir)); "error.invalid-app-image"), appImageDir,
AppImageFile.getPathInAppImage(appImageDir)));
} }
this.appVersion = appVersion;
this.launcherName = launcherName; this.launcherName = launcherName;
this.mainClass = mainClass; this.mainClass = mainClass;
this.addLauncherInfos = launcherInfos; this.addLauncherInfos = launcherInfos;
@ -132,6 +143,13 @@ public final class AppImageFile {
return addLauncherInfos; return addLauncherInfos;
} }
/**
* Returns application version. Never returns null or empty value.
*/
String getAppVersion() {
return appVersion;
}
/** /**
* Returns main application launcher name. Never returns null or empty value. * Returns main application launcher name. Never returns null or empty value.
*/ */
@ -150,7 +168,7 @@ public final class AppImageFile {
return signed; return signed;
} }
boolean isAppStore() { public boolean isAppStore() {
return appStore; return appStore;
} }
@ -165,53 +183,118 @@ public final class AppImageFile {
.resolve(FILENAME); .resolve(FILENAME);
} }
/**
* Saves file with application image info in application image using values
* from current instance.
* @param appImageDir - path to application image
* @throws IOException
*/
void save(Path appImageDir) throws IOException {
AppImageFile.save(appImageDir, null, this);
}
/** /**
* Saves file with application image info in application image. * Saves file with application image info in application image.
* @param appImageDir - path to application image * @param appImageDir - path to application image
* @param params - parameters used to generate application image
* @throws IOException * @throws IOException
*/ */
static void save(Path appImageDir, Map<String, Object> params) static void save(Path appImageDir, Map<String, Object> params)
throws IOException { throws IOException {
AppImageFile.save(appImageDir, params, null);
}
/**
* Saves file with application image info in application image using params
* or appImage. Both params or appImage cannot be valid.
* @param appImageDir - path to application image
* @param params - parameters used to generate application image
* @param appImage - instance of already existing application image file
* @throws IOException
* @throws IllegalArgumentException - If both params and appImage are null or
* If both params and appImage are not null
*/
private static void save(Path appImageDir,
Map<String, Object> params,
AppImageFile appImage) throws IOException {
if ((params == null && appImage == null) ||
(params != null && appImage != null)) {
throw new IllegalArgumentException();
}
final String appVersionSave;
final String mainLauncherSave;
final String mainClassSave;
final String signedSave;
final String appStoreSave;
final List<LauncherInfo> addLauncherInfoSave;
if (params != null) {
appVersionSave = VERSION.fetchFrom(params);
mainLauncherSave = APP_NAME.fetchFrom(params);
mainClassSave = MAIN_CLASS.fetchFrom(params);
signedSave = SIGN_BUNDLE.fetchFrom(params).toString();
appStoreSave = APP_STORE.fetchFrom(params).toString();
addLauncherInfoSave = null;
} else {
appVersionSave = appImage.getAppVersion();
mainLauncherSave = appImage.getLauncherName();
mainClassSave = appImage.getMainClass();
signedSave = String.valueOf(appImage.isSigned());
appStoreSave = String.valueOf(appImage.isAppStore());
addLauncherInfoSave = appImage.getAddLaunchers();
}
IOUtils.createXml(getPathInAppImage(appImageDir), xml -> { IOUtils.createXml(getPathInAppImage(appImageDir), xml -> {
xml.writeStartElement("jpackage-state"); xml.writeStartElement("jpackage-state");
xml.writeAttribute("version", getVersion()); xml.writeAttribute("version", getVersion());
xml.writeAttribute("platform", getPlatform()); xml.writeAttribute("platform", getPlatform());
xml.writeStartElement("app-version"); xml.writeStartElement("app-version");
xml.writeCharacters(VERSION.fetchFrom(params)); xml.writeCharacters(appVersionSave);
xml.writeEndElement(); xml.writeEndElement();
xml.writeStartElement("main-launcher"); xml.writeStartElement("main-launcher");
xml.writeCharacters(APP_NAME.fetchFrom(params)); xml.writeCharacters(mainLauncherSave);
xml.writeEndElement(); xml.writeEndElement();
xml.writeStartElement("main-class"); xml.writeStartElement("main-class");
xml.writeCharacters(MAIN_CLASS.fetchFrom(params)); xml.writeCharacters(mainClassSave);
xml.writeEndElement(); xml.writeEndElement();
xml.writeStartElement("signed"); xml.writeStartElement("signed");
xml.writeCharacters(SIGN_BUNDLE.fetchFrom(params).toString()); xml.writeCharacters(signedSave);
xml.writeEndElement(); xml.writeEndElement();
xml.writeStartElement("app-store"); xml.writeStartElement("app-store");
xml.writeCharacters(APP_STORE.fetchFrom(params).toString()); xml.writeCharacters(appStoreSave);
xml.writeEndElement(); xml.writeEndElement();
List<Map<String, ? super Object>> addLaunchers = if (addLauncherInfoSave != null) {
ADD_LAUNCHERS.fetchFrom(params); for (var li : addLauncherInfoSave) {
addLauncherInfo(xml, li);
}
} else {
List<Map<String, ? super Object>> addLaunchers =
ADD_LAUNCHERS.fetchFrom(params);
for (var launcherParams : addLaunchers) { for (var launcherParams : addLaunchers) {
var li = new LauncherInfo(launcherParams); var li = new LauncherInfo(launcherParams);
xml.writeStartElement("add-launcher"); addLauncherInfo(xml, li);
xml.writeAttribute("name", li.getName()); }
xml.writeAttribute("shortcut", Boolean.toString(li.isShortcut()));
xml.writeAttribute("menu", Boolean.toString(li.isMenu()));
xml.writeAttribute("service", Boolean.toString(li.isService()));
xml.writeEndElement();
} }
}); });
} }
static void addLauncherInfo(XMLStreamWriter xml, LauncherInfo li)
throws XMLStreamException {
xml.writeStartElement("add-launcher");
xml.writeAttribute("name", li.getName());
xml.writeAttribute("shortcut", Boolean.toString(li.isShortcut()));
xml.writeAttribute("menu", Boolean.toString(li.isMenu()));
xml.writeAttribute("service", Boolean.toString(li.isService()));
xml.writeEndElement();
}
/** /**
* Loads application image info from application image. * Loads application image info from application image.
* @param appImageDir - path to application image * @param appImageDir - path to application image
@ -224,6 +307,9 @@ public final class AppImageFile {
XPath xPath = XPathFactory.newInstance().newXPath(); XPath xPath = XPathFactory.newInstance().newXPath();
String appVersion = xpathQueryNullable(xPath,
"/jpackage-state/app-version/text()", doc);
String mainLauncher = xpathQueryNullable(xPath, String mainLauncher = xpathQueryNullable(xPath,
"/jpackage-state/main-launcher/text()", doc); "/jpackage-state/main-launcher/text()", doc);
@ -252,8 +338,9 @@ public final class AppImageFile {
launcherInfos.add(new LauncherInfo(launcherNodes.item(i))); launcherInfos.add(new LauncherInfo(launcherNodes.item(i)));
} }
return new AppImageFile(appImageDir, mainLauncher, mainClass, return new AppImageFile(appImageDir, appVersion, mainLauncher,
launcherInfos, version, platform, signedStr, appStoreStr); mainClass, launcherInfos, version, platform, signedStr,
appStoreStr);
} catch (XPathExpressionException ex) { } catch (XPathExpressionException ex) {
// This should never happen as XPath expressions should be correct // This should never happen as XPath expressions should be correct
throw new RuntimeException(ex); throw new RuntimeException(ex);
@ -266,10 +353,22 @@ public final class AppImageFile {
} }
} }
private static String getAttribute(Node item, String attr) { /**
NamedNodeMap attrs = item.getAttributes(); * Returns copy of AppImageFile, but with signed set to true if AppImageFile
Node attrNode = attrs.getNamedItem(attr); * is not marked as signed. If AppImageFile already signed it will return
return ((attrNode == null) ? null : attrNode.getNodeValue()); * instance to itself.
*/
public AppImageFile copyAsSigned() {
if (isSigned()) {
return this;
}
// Pass null for appImageDir, it is used only to show location of
// .jpackage.xml in case of error. copyAsSigned() should not produce
// invalid app image file.
return new AppImageFile(null, getAppVersion(),
getLauncherName(), getMainClass(), getAddLaunchers(),
getVersion(), getPlatform(), "true", String.valueOf(isAppStore()));
} }
public static Document readXml(Path appImageDir) throws IOException { public static Document readXml(Path appImageDir) throws IOException {
@ -362,6 +461,12 @@ public final class AppImageFile {
this.service = !"false".equals(getAttribute(node, "service")); this.service = !"false".equals(getAttribute(node, "service"));
} }
private String getAttribute(Node item, String attr) {
NamedNodeMap attrs = item.getAttributes();
Node attrNode = attrs.getNamedItem(attr);
return ((attrNode == null) ? null : attrNode.getNodeValue());
}
public String getName() { public String getName() {
return name; return name;
} }

View File

@ -78,8 +78,8 @@ error.no.name=Name not specified with --name and cannot infer one from app-image
warning.no.jdk.modules.found=Warning: No JDK Modules found warning.no.jdk.modules.found=Warning: No JDK Modules found
error.foreign-app-image=Error: Missing .jpackage.xml file in app-image dir ({0}) error.foreign-app-image=Error: Missing .jpackage.xml file in app-image dir "{0}"
error.invalid-app-image=Error: app-image dir ({0}) generated by another jpackage version or malformed .jpackage.xml error.invalid-app-image=Error: app-image dir "{0}" generated by another jpackage version or malformed "{1}"
MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\ MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\

View File

@ -76,8 +76,8 @@ error.no.name=Name nicht mit --name angegeben. Es kann auch kein Name aus app-im
warning.no.jdk.modules.found=Warnung: Keine JDK-Module gefunden warning.no.jdk.modules.found=Warnung: Keine JDK-Module gefunden
error.foreign-app-image=Error: Missing .jpackage.xml file in app-image dir ({0}) error.foreign-app-image=Error: Missing .jpackage.xml file in app-image dir "{0}"
error.invalid-app-image=Error: app-image dir ({0}) generated by another jpackage version or malformed .jpackage.xml error.invalid-app-image=Error: app-image dir "{0}" generated by another jpackage version or malformed "{1}"
MSG_BundlerFailed=Fehler: Bundler "{1}" ({0}) konnte kein Package generieren MSG_BundlerFailed=Fehler: Bundler "{1}" ({0}) konnte kein Package generieren
MSG_BundlerConfigException=Bundler {0} aufgrund eines Konfigurationsproblems \u00FCbersprungen: {1} \nEmpfehlung zur Behebung: {2} MSG_BundlerConfigException=Bundler {0} aufgrund eines Konfigurationsproblems \u00FCbersprungen: {1} \nEmpfehlung zur Behebung: {2}

View File

@ -76,8 +76,8 @@ error.no.name=\u540D\u524D\u304C--name\u3067\u6307\u5B9A\u3055\u308C\u3066\u304A
warning.no.jdk.modules.found=\u8B66\u544A: JDK\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 warning.no.jdk.modules.found=\u8B66\u544A: JDK\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093
error.foreign-app-image=Error: Missing .jpackage.xml file in app-image dir ({0}) error.foreign-app-image=Error: Missing .jpackage.xml file in app-image dir "{0}"
error.invalid-app-image=Error: app-image dir ({0}) generated by another jpackage version or malformed .jpackage.xml error.invalid-app-image=Error: app-image dir "{0}" generated by another jpackage version or malformed "{1}"
MSG_BundlerFailed=\u30A8\u30E9\u30FC: \u30D0\u30F3\u30C9\u30E9"{1}" ({0})\u304C\u30D1\u30C3\u30B1\u30FC\u30B8\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F MSG_BundlerFailed=\u30A8\u30E9\u30FC: \u30D0\u30F3\u30C9\u30E9"{1}" ({0})\u304C\u30D1\u30C3\u30B1\u30FC\u30B8\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F
MSG_BundlerConfigException=\u69CB\u6210\u306E\u554F\u984C\u306E\u305F\u3081\u3001\u30D0\u30F3\u30C9\u30E9{0}\u304C\u30B9\u30AD\u30C3\u30D7\u3055\u308C\u307E\u3057\u305F: {1} \n\u6B21\u306E\u4FEE\u6B63\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044: {2} MSG_BundlerConfigException=\u69CB\u6210\u306E\u554F\u984C\u306E\u305F\u3081\u3001\u30D0\u30F3\u30C9\u30E9{0}\u304C\u30B9\u30AD\u30C3\u30D7\u3055\u308C\u307E\u3057\u305F: {1} \n\u6B21\u306E\u4FEE\u6B63\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044: {2}

View File

@ -76,8 +76,8 @@ error.no.name=\u672A\u4F7F\u7528 --name \u6307\u5B9A\u540D\u79F0\uFF0C\u65E0\u6C
warning.no.jdk.modules.found=\u8B66\u544A: \u672A\u627E\u5230 JDK \u6A21\u5757 warning.no.jdk.modules.found=\u8B66\u544A: \u672A\u627E\u5230 JDK \u6A21\u5757
error.foreign-app-image=Error: Missing .jpackage.xml file in app-image dir ({0}) error.foreign-app-image=Error: Missing .jpackage.xml file in app-image dir "{0}"
error.invalid-app-image=Error: app-image dir ({0}) generated by another jpackage version or malformed .jpackage.xml error.invalid-app-image=Error: app-image dir "{0}" generated by another jpackage version or malformed "{1}"
MSG_BundlerFailed=\u9519\u8BEF\uFF1A\u6253\u5305\u7A0B\u5E8F "{1}" ({0}) \u65E0\u6CD5\u751F\u6210\u7A0B\u5E8F\u5305 MSG_BundlerFailed=\u9519\u8BEF\uFF1A\u6253\u5305\u7A0B\u5E8F "{1}" ({0}) \u65E0\u6CD5\u751F\u6210\u7A0B\u5E8F\u5305
MSG_BundlerConfigException=\u7531\u4E8E\u914D\u7F6E\u95EE\u9898, \u8DF3\u8FC7\u4E86\u6253\u5305\u7A0B\u5E8F{0}: {1} \n\u4FEE\u590D\u5EFA\u8BAE: {2} MSG_BundlerConfigException=\u7531\u4E8E\u914D\u7F6E\u95EE\u9898, \u8DF3\u8FC7\u4E86\u6253\u5305\u7A0B\u5E8F{0}: {1} \n\u4FEE\u590D\u5EFA\u8BAE: {2}

View File

@ -395,10 +395,16 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
* Returns path to output bundle of configured jpackage command. * Returns path to output bundle of configured jpackage command.
* *
* If this is build image command, returns path to application image directory. * If this is build image command, returns path to application image directory.
*
* Special case for masOS. If this is sign app image command, returns value
* of "--app-image".
*/ */
public Path outputBundle() { public Path outputBundle() {
final String bundleName; final String bundleName;
if (isImagePackageType()) { if (isImagePackageType()) {
if (TKit.isOSX() && hasArgument("--app-image")) {
return Path.of(getArgumentValue("--app-image", () -> null));
}
String dirName = name(); String dirName = name();
if (TKit.isOSX()) { if (TKit.isOSX()) {
dirName = dirName + ".app"; dirName = dirName + ".app";
@ -818,12 +824,34 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
} }
private void assertAppImageFile() { private void assertAppImageFile() {
final Path lookupPath = AppImageFile.getPathInAppImage(Path.of("")); Path appImageDir = Path.of("");
if (isImagePackageType() && hasArgument("--app-image")) {
appImageDir = Path.of(getArgumentValue("--app-image", () -> null));
}
final Path lookupPath = AppImageFile.getPathInAppImage(appImageDir);
if (isRuntime() || (!isImagePackageType() && !TKit.isOSX())) { if (isRuntime() || (!isImagePackageType() && !TKit.isOSX())) {
assertFileInAppImage(lookupPath, null); assertFileInAppImage(lookupPath, null);
} else { } else {
assertFileInAppImage(lookupPath, lookupPath); assertFileInAppImage(lookupPath, lookupPath);
// If file exist validated important values based on arguments
// Exclude validation when we generating packages from predefined
// app images, since we do not know if image is signed or not.
if (isImagePackageType() || !hasArgument("--app-image")) {
final Path rootDir = isImagePackageType() ? outputBundle() :
pathToUnpackedPackageFile(appInstallationDirectory());
boolean expectedValue = hasArgument("--mac-sign");
boolean actualValue = AppImageFile.load(rootDir).isSigned();
TKit.assertTrue(expectedValue == actualValue,
"Unexptected value in app image file for <signed>");
expectedValue = hasArgument("--mac-app-store");
actualValue = AppImageFile.load(rootDir).isAppStore();
TKit.assertTrue(expectedValue == actualValue,
"Unexptected value in app image file for <app-store>");
}
} }
} }

View File

@ -75,7 +75,7 @@ public class AppImageFileTest {
params = new LinkedHashMap<>(); params = new LinkedHashMap<>();
params.put(Arguments.CLIOptions.NAME.getId(), "foo"); params.put(Arguments.CLIOptions.NAME.getId(), "foo");
params.put(Arguments.CLIOptions.APPCLASS.getId(), "TestClass"); params.put(Arguments.CLIOptions.APPCLASS.getId(), "TestClass");
params.put(Arguments.CLIOptions.VERSION.getId(), ""); params.put(Arguments.CLIOptions.VERSION.getId(), "1.0");
create(params); create(params);
} }
@ -104,6 +104,7 @@ public class AppImageFileTest {
public void testValidXml() throws IOException { public void testValidXml() throws IOException {
Assert.assertEquals("Foo", (createFromXml( Assert.assertEquals("Foo", (createFromXml(
JPACKAGE_STATE_OPEN, JPACKAGE_STATE_OPEN,
"<app-version>1.0</app-version>",
"<main-launcher>Foo</main-launcher>", "<main-launcher>Foo</main-launcher>",
"<main-class>main.Class</main-class>", "<main-class>main.Class</main-class>",
"<signed>false</signed>", "<signed>false</signed>",
@ -112,6 +113,7 @@ public class AppImageFileTest {
Assert.assertEquals("Boo", (createFromXml( Assert.assertEquals("Boo", (createFromXml(
JPACKAGE_STATE_OPEN, JPACKAGE_STATE_OPEN,
"<app-version>1.0</app-version>",
"<main-launcher>Boo</main-launcher>", "<main-launcher>Boo</main-launcher>",
"<main-launcher>Bar</main-launcher>", "<main-launcher>Bar</main-launcher>",
"<main-class>main.Class</main-class>", "<main-class>main.Class</main-class>",
@ -121,6 +123,7 @@ public class AppImageFileTest {
var file = createFromXml( var file = createFromXml(
JPACKAGE_STATE_OPEN, JPACKAGE_STATE_OPEN,
"<app-version>1.0</app-version>",
"<main-launcher>Foo</main-launcher>", "<main-launcher>Foo</main-launcher>",
"<main-class>main.Class</main-class>", "<main-class>main.Class</main-class>",
"<signed>false</signed>", "<signed>false</signed>",
@ -166,6 +169,21 @@ public class AppImageFileTest {
Assert.assertTrue(aif.isSigned()); Assert.assertTrue(aif.isSigned());
} }
@Test
public void testCopyAsSigned() throws IOException {
Map<String, Object> params = new LinkedHashMap<>();
params.put("name", "Foo");
params.put("main-class", "main.Class");
params.put("description", "Duck App Description");
params.put("mac-sign", Boolean.FALSE);
AppImageFile aif = create(params);
Assert.assertFalse(aif.isSigned());
aif = aif.copyAsSigned();
Assert.assertTrue(aif.isSigned());
}
@Test @Test
public void testMacAppStore() throws IOException { public void testMacAppStore() throws IOException {
Map<String, Object> params = new LinkedHashMap<>(); Map<String, Object> params = new LinkedHashMap<>();
@ -215,7 +233,10 @@ public class AppImageFileTest {
private void assertInvalid(ThrowingRunnable action) { private void assertInvalid(ThrowingRunnable action) {
Exception ex = Assert.assertThrows(RuntimeException.class, action); Exception ex = Assert.assertThrows(RuntimeException.class, action);
Assert.assertTrue(ex instanceof RuntimeException); Assert.assertTrue(ex instanceof RuntimeException);
Assert.assertTrue(ex.getMessage().contains("malformed .jpackage.xml")); Assert.assertTrue(ex.getMessage()
.contains("generated by another jpackage version or malformed"));
Assert.assertTrue(ex.getMessage()
.endsWith(".jpackage.xml\""));
} }
private AppImageFile createFromXml(String... xmlData) throws IOException { private AppImageFile createFromXml(String... xmlData) throws IOException {

View File

@ -88,7 +88,7 @@ public class SigningAppImageTwoStepsTest {
appImageCmd.executeAndAssertHelloAppImageCreated(); appImageCmd.executeAndAssertHelloAppImageCreated();
// Double check if it is signed or unsigned based on signAppImage // Double check if it is signed or unsigned based on signAppImage
verifySignature(appImageCmd, signAppImage); SigningBase.verifyAppImageSignature(appImageCmd, signAppImage, "testAL");
// Sign app image // Sign app image
JPackageCommand cmd = new JPackageCommand(); JPackageCommand cmd = new JPackageCommand();
@ -97,24 +97,9 @@ public class SigningAppImageTwoStepsTest {
.addArguments("--mac-sign") .addArguments("--mac-sign")
.addArguments("--mac-signing-key-user-name", SigningBase.DEV_NAME) .addArguments("--mac-signing-key-user-name", SigningBase.DEV_NAME)
.addArguments("--mac-signing-keychain", SigningBase.KEYCHAIN); .addArguments("--mac-signing-keychain", SigningBase.KEYCHAIN);
cmd.execute(); cmd.executeAndAssertImageCreated();
// Should be signed app image // Should be signed app image
verifySignature(appImageCmd, true); SigningBase.verifyAppImageSignature(appImageCmd, true, "testAL");
}
private void verifySignature(JPackageCommand appImageCmd, boolean isSigned) throws Exception {
Path launcherPath = appImageCmd.appLauncherPath();
SigningBase.verifyCodesign(launcherPath, isSigned);
Path testALPath = launcherPath.getParent().resolve("testAL");
SigningBase.verifyCodesign(testALPath, isSigned);
Path appImage = appImageCmd.outputBundle();
SigningBase.verifyCodesign(appImage, isSigned);
if (isSigned) {
SigningBase.verifySpctl(appImage, "exec");
}
} }
} }

View File

@ -0,0 +1,159 @@
/*
* 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;
/**
* Tests generation of dmg and pkg from signed predefined app image which was
* signed using two step process (generate app image and then signed using
* --app-image and --mac-sign). Test will generate pkg and verifies its
* signature. It verifies that dmg is not signed, but app image inside dmg
* is signed. 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 SigningPackageFromTwoStepAppImageTest
* @modules jdk.jpackage/jdk.jpackage.internal
* @requires (os.family == "mac")
* @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=SigningPackageFromTwoStepAppImageTest
*/
public class SigningPackageFromTwoStepAppImageTest {
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")) {
Path launcherPath = ApplicationLayout.platformAppImage()
.resolveAt(dmgImage).launchersDirectory().resolve(cmd.name());
SigningBase.verifyCodesign(launcherPath, true);
SigningBase.verifyCodesign(dmgImage, true);
SigningBase.verifySpctl(dmgImage, "exec");
}
});
}
@Test
@Parameter("true")
@Parameter("false")
public static void test(boolean signAppImage) throws Exception {
SigningCheck.checkCertificates();
Path appimageOutput = TKit.createTempDirectory("appimage");
// Generate app image. Signed or unsigned based on test
// parameter. We should able to sign predfined app images
// which are signed or unsigned.
JPackageCommand appImageCmd = JPackageCommand.helloAppImage()
.setArgumentValue("--dest", appimageOutput);
if (signAppImage) {
appImageCmd.addArguments("--mac-sign", "--mac-signing-key-user-name",
SigningBase.DEV_NAME, "--mac-signing-keychain",
SigningBase.KEYCHAIN);
}
// Generate app image
appImageCmd.executeAndAssertHelloAppImageCreated();
// Double check if it is signed or unsigned based on signAppImage
SigningBase.verifyAppImageSignature(appImageCmd, signAppImage);
// Sign app image
JPackageCommand appImageSignedCmd = new JPackageCommand();
appImageSignedCmd.setPackageType(PackageType.IMAGE)
.addArguments("--app-image", appImageCmd.outputBundle().toAbsolutePath())
.addArguments("--mac-sign")
.addArguments("--mac-signing-key-user-name", SigningBase.DEV_NAME)
.addArguments("--mac-signing-keychain", SigningBase.KEYCHAIN);
appImageSignedCmd.executeAndAssertImageCreated();
// Should be signed app image
SigningBase.verifyAppImageSignature(appImageCmd, true);
new PackageTest()
.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(
SigningPackageFromTwoStepAppImageTest::verifyPKG)
.forTypes(PackageType.MAC_DMG)
.addBundleVerifier(
SigningPackageFromTwoStepAppImageTest::verifyDMG)
.addBundleVerifier(
SigningPackageFromTwoStepAppImageTest::verifyAppImageInDMG)
.run();
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019, 2020, 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -24,6 +24,7 @@
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit; import jdk.jpackage.test.TKit;
import jdk.jpackage.test.Executor; import jdk.jpackage.test.Executor;
import jdk.jpackage.test.Executor.Result; import jdk.jpackage.test.Executor.Result;
@ -147,4 +148,22 @@ public class SigningBase {
verifyPkgutilResult(result); verifyPkgutilResult(result);
} }
public static void verifyAppImageSignature(JPackageCommand appImageCmd,
boolean isSigned, String... launchers) throws Exception {
Path launcherPath = appImageCmd.appLauncherPath();
SigningBase.verifyCodesign(launcherPath, isSigned);
final List<String> launchersList = List.of(launchers);
launchersList.forEach(launcher -> {
Path testALPath = launcherPath.getParent().resolve(launcher);
SigningBase.verifyCodesign(testALPath, isSigned);
});
Path appImage = appImageCmd.outputBundle();
SigningBase.verifyCodesign(appImage, isSigned);
if (isSigned) {
SigningBase.verifySpctl(appImage, "exec");
}
}
} }