From 2aa0efd4256a61e1e20989973f32be5d8e8f8fe3 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 21 Oct 2025 06:21:11 +0000 Subject: [PATCH] 8370126: Improve jpackage signing testing Reviewed-by: almatvee --- .../internal/MacBaseInstallerBundler.java | 27 +- .../jpackage/internal/MacPackageBuilder.java | 18 +- .../internal/MacPkgPackageBuilder.java | 12 +- .../jdk/jpackage/test/JPackageCommand.java | 5 + .../helpers/jdk/jpackage/test/MacHelper.java | 136 +++++++- .../helpers/jdk/jpackage/test/MacSign.java | 106 +++++- .../jdk/jpackage/test/MacSignVerify.java | 75 ++++- .../tools/jpackage/macosx/MacSignTest.java | 70 ++-- .../jpackage/macosx/SigningAppImageTest.java | 18 +- .../macosx/SigningAppImageTwoStepsTest.java | 26 +- ...SigningPackageFromTwoStepAppImageTest.java | 25 +- .../jpackage/macosx/SigningPackageTest.java | 17 +- .../macosx/SigningPackageTwoStepTest.java | 308 +++++++++++------- .../SigningRuntimeImagePackageTest.java | 60 ++-- .../jpackage/macosx/base/SigningBase.java | 51 ++- 15 files changed, 682 insertions(+), 272 deletions(-) diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java index f46b5a328fd..5c912728c32 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java @@ -26,14 +26,8 @@ package jdk.jpackage.internal; import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE; -import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; import java.util.Map; -import java.util.Optional; import jdk.jpackage.internal.model.ConfigException; public abstract class MacBaseInstallerBundler extends AbstractBundler { @@ -44,26 +38,7 @@ public abstract class MacBaseInstallerBundler extends AbstractBundler { protected void validateAppImageAndBundeler( Map params) throws ConfigException { - if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) { - Path applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params); - if (new MacAppImageFileExtras(PREDEFINED_APP_IMAGE_FILE.fetchFrom(params)).signed()) { - var appLayout = ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT.resolveAt(applicationImage); - if (!Files.exists( - PackageFile.getPathInAppImage(appLayout))) { - Log.info(MessageFormat.format(I18N.getString( - "warning.per.user.app.image.signed"), - PackageFile.getPathInAppImage(appLayout))); - } - } 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())); - } - } - } else { + if (PREDEFINED_APP_IMAGE.fetchFrom(params) == null) { appImageBundler.validate(params); } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java index cf5c6a934f7..9576f6a6a99 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java @@ -24,8 +24,10 @@ */ package jdk.jpackage.internal; +import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; import static jdk.jpackage.internal.MacPackagingPipeline.LayoutUtils.packagerLayout; +import java.nio.file.Files; import java.util.Objects; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.MacApplication; @@ -57,7 +59,21 @@ final class MacPackageBuilder { .installedPackageLayout(pkg.installedPackageLayout()); pkg = pkgBuilder.create(); - return MacPackage.create(pkg, new MacPackageMixin.Stub(pkg.predefinedAppImage().map(v -> predefinedAppImageSigned))); + + var macPkg = MacPackage.create(pkg, new MacPackageMixin.Stub(pkg.predefinedAppImage().map(v -> predefinedAppImageSigned))); + validatePredefinedAppImage(macPkg); + return macPkg; + } + + private static void validatePredefinedAppImage(MacPackage pkg) { + if (pkg.predefinedAppImageSigned().orElse(false)) { + pkg.predefinedAppImage().ifPresent(predefinedAppImage -> { + var thePackageFile = PackageFile.getPathInAppImage(APPLICATION_LAYOUT); + if (!Files.exists(predefinedAppImage.resolve(thePackageFile))) { + Log.info(I18N.format("warning.per.user.app.image.signed", thePackageFile)); + } + }); + } } private final PackageBuilder pkgBuilder; diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java index 131650aebb5..663b8b16265 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java @@ -43,7 +43,9 @@ final class MacPkgPackageBuilder { } MacPkgPackage create() throws ConfigException { - return MacPkgPackage.create(pkgBuilder.create(), new MacPkgPackageMixin.Stub(createSigningConfig())); + var pkg = MacPkgPackage.create(pkgBuilder.create(), new MacPkgPackageMixin.Stub(createSigningConfig())); + validatePredefinedAppImage(pkg); + return pkg; } private Optional createSigningConfig() throws ConfigException { @@ -56,6 +58,14 @@ final class MacPkgPackageBuilder { } } + private static void validatePredefinedAppImage(MacPkgPackage pkg) { + if (!pkg.predefinedAppImageSigned().orElse(false) && pkg.sign()) { + pkg.predefinedAppImage().ifPresent(predefinedAppImage -> { + Log.info(I18N.format("warning.unsigned.app.image", "pkg")); + }); + } + } + private final MacPackageBuilder pkgBuilder; private SigningIdentityBuilder signingBuilder; } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index ae9568bb844..6945cd2b722 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -1131,6 +1131,11 @@ public class JPackageCommand extends CommandArguments { MacHelper.verifyBundleStructure(cmd); } }), + MAC_BUNDLE_UNSIGNED_SIGNATURE(cmd -> { + if (TKit.isOSX() && !MacHelper.appImageSigned(cmd)) { + MacHelper.verifyUnsignedBundleSignature(cmd); + } + }), ; AppLayoutAssert(Consumer action) { diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java index d01536e327d..a7a69ef0329 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java @@ -52,6 +52,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.regex.Pattern; @@ -66,6 +67,7 @@ import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.XmlUtils; import jdk.jpackage.internal.util.function.ThrowingConsumer; import jdk.jpackage.internal.util.function.ThrowingSupplier; +import jdk.jpackage.test.MacSign.CertificateRequest; import jdk.jpackage.test.PackageTest.PackageHandlers; public final class MacHelper { @@ -229,25 +231,61 @@ public final class MacHelper { } } + /** + * Returns {@code true} if the given jpackage command line is configured to sign + * predefined app image in place. + *

+ * jpackage will not create a new app image or a native bundle. + * + * @param cmd the jpackage command to examine + * @return {@code true} if the given jpackage command line is configured to sign + * predefined app image in place and {@code false} otherwise. + */ public static boolean signPredefinedAppImage(JPackageCommand cmd) { Objects.requireNonNull(cmd); if (!TKit.isOSX()) { throw new UnsupportedOperationException(); } - return cmd.hasArgument("--mac-sign") && cmd.hasArgument("--app-image"); + return cmd.hasArgument("--mac-sign") && cmd.hasArgument("--app-image") && cmd.isImagePackageType(); } + /** + * Returns {@code true} if the given jpackage command line is configured such + * that the app image it will produce will be signed. + *

+ * If the jpackage command line is bundling a native package, the function + * returns {@code true} if the bundled app image will be signed. + * + * @param cmd the jpackage command to examine + * @return {@code true} if the given jpackage command line is configured such + * that the app image it will produce will be signed and {@code false} + * otherwise. + */ public static boolean appImageSigned(JPackageCommand cmd) { Objects.requireNonNull(cmd); if (!TKit.isOSX()) { throw new UnsupportedOperationException(); } - if (Optional.ofNullable(cmd.getArgumentValue("--app-image")).map(Path::of).map(AppImageFile::load).map(AppImageFile::macSigned).orElse(false)) { + var runtimeImage = Optional.ofNullable(cmd.getArgumentValue("--runtime-image")).map(Path::of); + var appImage = Optional.ofNullable(cmd.getArgumentValue("--app-image")).map(Path::of); + + if (cmd.isRuntime() && Files.isDirectory(runtimeImage.orElseThrow().resolve("Contents/_CodeSignature"))) { + // If the predefined runtime is a signed bundle, bundled image should be signed too. + return true; + } else if (appImage.map(AppImageFile::load).map(AppImageFile::macSigned).orElse(false)) { // The external app image is signed, so the app image is signed too. return true; } + if (!cmd.isImagePackageType() && appImage.isPresent()) { + // Building a ".pkg" or a ".dmg" bundle from the predefined app image. + // The predefined app image is unsigned, so the app image bundled + // in the output native package will be unsigned too + // (even if the ".pkg" file may be signed itself, and we never sign ".dmg" files). + return false; + } + if (!cmd.hasArgument("--mac-sign")) { return false; } @@ -332,6 +370,100 @@ public final class MacHelper { }).run(); } + public static JPackageCommand useKeychain(JPackageCommand cmd, MacSign.ResolvedKeychain keychain) { + return useKeychain(cmd, keychain.spec().keychain()); + } + + public static JPackageCommand useKeychain(JPackageCommand cmd, MacSign.Keychain keychain) { + return sign(cmd).addArguments("--mac-signing-keychain", keychain.name()); + } + + public static JPackageCommand sign(JPackageCommand cmd) { + if (!cmd.hasArgument("--mac-sign")) { + cmd.addArgument("--mac-sign"); + } + return cmd; + } + + public record SignKeyOption(Type type, CertificateRequest certRequest) { + + public SignKeyOption { + Objects.requireNonNull(type); + Objects.requireNonNull(certRequest); + } + + public enum Type { + SIGN_KEY_USER_NAME, + SIGN_KEY_IDENTITY, + ; + } + + @Override + public String toString() { + var sb = new StringBuffer(); + applyTo((optionName, _) -> { + sb.append(String.format("{%s: %s}", optionName, certRequest)); + }); + return sb.toString(); + } + + public JPackageCommand addTo(JPackageCommand cmd) { + applyTo(cmd::addArguments); + return sign(cmd); + } + + public JPackageCommand setTo(JPackageCommand cmd) { + applyTo(cmd::setArgumentValue); + return sign(cmd); + } + + private void applyTo(BiConsumer sink) { + switch (certRequest.type()) { + case INSTALLER -> { + switch (type) { + case SIGN_KEY_IDENTITY -> { + sink.accept("--mac-installer-sign-identity", certRequest.name()); + return; + } + case SIGN_KEY_USER_NAME -> { + sink.accept("--mac-signing-key-user-name", certRequest.shortName()); + return; + } + } + } + case CODE_SIGN -> { + switch (type) { + case SIGN_KEY_IDENTITY -> { + sink.accept("--mac-app-image-sign-identity", certRequest.name()); + return; + } + case SIGN_KEY_USER_NAME -> { + sink.accept("--mac-signing-key-user-name", certRequest.shortName()); + return; + } + } + } + } + + throw new AssertionError(); + } + } + + static void verifyUnsignedBundleSignature(JPackageCommand cmd) { + if (!cmd.isImagePackageType()) { + MacSignVerify.assertUnsigned(cmd.outputBundle()); + } + + final Path bundleRoot; + if (cmd.isImagePackageType()) { + bundleRoot = cmd.outputBundle(); + } else { + bundleRoot = cmd.pathToUnpackedPackageFile(cmd.appInstallationDirectory()); + } + + MacSignVerify.assertAdhocSigned(bundleRoot); + } + static PackageHandlers createDmgPackageHandlers() { return new PackageHandlers(MacHelper::installDmg, MacHelper::uninstallDmg, MacHelper::unpackDmg); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSign.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSign.java index af9f57c4f7f..7d2bb908edb 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSign.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSign.java @@ -59,6 +59,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; import javax.naming.ldap.LdapName; @@ -351,6 +352,52 @@ public final class MacSign { private String password; } + public static final class UsageBuilder { + + UsageBuilder(Collection keychains) { + this.keychains = List.copyOf(keychains); + } + + public void run(Runnable runnable) { + Objects.requireNonNull(runnable); + + final Optional> oldKeychains; + if (addToSearchList) { + oldKeychains = Optional.ofNullable(activeKeychainFiles()); + Keychain.addToSearchList(keychains); + } else { + oldKeychains = Optional.empty(); + } + + try { + // Ensure keychains to be used for signing are unlocked. + // When the codesign command operates on a locked keychain in a ssh session + // it emits cryptic "errSecInternalComponent" error without other details. + keychains.forEach(Keychain::unlock); + runnable.run(); + } finally { + oldKeychains.ifPresent(restoreKeychains -> { + security("list-keychains", "-d", "user", "-s") + .addArguments(restoreKeychains.stream().map(Path::toString).toList()) + .execute(); + }); + } + } + + public UsageBuilder addToSearchList(boolean v) { + addToSearchList = v; + return this; + } + + public UsageBuilder addToSearchList() { + return addToSearchList(true); + } + + private final Collection keychains; + private boolean addToSearchList; + } + + Keychain create() { final var exec = createExecutor("create-keychain"); final var result = exec.saveOutput().executeWithoutExitCodeCheck(); @@ -415,24 +462,12 @@ public final class MacSign { return certs; } - public static void addToSearchList(Collection keychains) { + static void addToSearchList(Collection keychains) { security("list-keychains", "-d", "user", "-s", "login.keychain") .addArguments(keychains.stream().map(Keychain::name).toList()) .execute(); } - public static void withAddedKeychains(Collection keychains, Runnable runnable) { - final var curKeychains = activeKeychainFiles(); - addToSearchList(keychains); - try { - runnable.run(); - } finally { - security("list-keychains", "-d", "user", "-s") - .addArguments(curKeychains.stream().map(Path::toString).toList()) - .execute(); - } - } - private static List activeKeychainFiles() { // $ security list-keychains // "/Users/alexeysemenyuk/Library/Keychains/login.keychain-db" @@ -1037,6 +1072,47 @@ public final class MacSign { return !missingKeychain && !missingCertificates && !invalidCertificates; } + public static Keychain.UsageBuilder withKeychains(KeychainWithCertsSpec... keychains) { + return withKeychains(Stream.of(keychains).map(KeychainWithCertsSpec::keychain).toArray(Keychain[]::new)); + } + + public static Keychain.UsageBuilder withKeychains(Keychain... keychains) { + return new Keychain.UsageBuilder(List.of(keychains)); + } + + public static void withKeychains(Runnable runnable, Consumer mutator, Keychain... keychains) { + Objects.requireNonNull(runnable); + var builder = withKeychains(keychains); + mutator.accept(builder); + builder.run(runnable); + } + + public static void withKeychains(Runnable runnable, Keychain... keychains) { + withKeychains(runnable, _ -> {}, keychains); + } + + public static void withKeychain(Consumer consumer, Consumer mutator, Keychain keychain) { + Objects.requireNonNull(consumer); + withKeychains(() -> { + consumer.accept(keychain); + }, mutator, keychain); + } + + public static void withKeychain(Consumer consumer, Keychain keychain) { + withKeychain(consumer, _ -> {}, keychain); + } + + public static void withKeychain(Consumer consumer, Consumer mutator, ResolvedKeychain keychain) { + Objects.requireNonNull(consumer); + withKeychains(() -> { + consumer.accept(keychain); + }, mutator, keychain.spec().keychain()); + } + + public static void withKeychain(Consumer consumer, ResolvedKeychain keychain) { + withKeychain(consumer, _ -> {}, keychain); + } + public static final class ResolvedKeychain { public ResolvedKeychain(KeychainWithCertsSpec spec) { this.spec = Objects.requireNonNull(spec); @@ -1046,6 +1122,10 @@ public final class MacSign { return spec; } + public String name() { + return spec.keychain().name(); + } + public Map mapCertificateRequests() { if (certMap == null) { synchronized (this) { diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSignVerify.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSignVerify.java index ae27e292bf6..81d31ed7267 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSignVerify.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSignVerify.java @@ -44,6 +44,43 @@ import jdk.jpackage.test.MacSign.CertificateRequest; */ public final class MacSignVerify { + public static void verifyAppImageSigned( + JPackageCommand cmd, CertificateRequest certRequest, MacSign.ResolvedKeychain keychain) { + + cmd.verifyIsOfType(PackageType.MAC); + Objects.requireNonNull(certRequest); + Objects.requireNonNull(keychain); + + final Path bundleRoot; + if (cmd.isImagePackageType()) { + bundleRoot = cmd.outputBundle(); + } else { + bundleRoot = cmd.pathToUnpackedPackageFile( + cmd.appInstallationDirectory()); + } + + assertSigned(bundleRoot, certRequest); + + if (!cmd.isRuntime()) { + cmd.addLauncherNames().stream().map(cmd::appLauncherPath).forEach(launcherPath -> { + assertSigned(launcherPath, certRequest); + }); + } + + // Set to "null" if the sign origin is not found, instead of bailing out with an exception. + // Let is fail in the following TKit.assertEquals() call with a proper log message. + var signOrigin = findSpctlSignOrigin(SpctlType.EXEC, bundleRoot).orElse(null); + + TKit.assertEquals(certRequest.name(), signOrigin, + String.format("Check [%s] has sign origin as expected", bundleRoot)); + } + + public static void verifyPkgSigned(JPackageCommand cmd, CertificateRequest certRequest, MacSign.ResolvedKeychain keychain) { + cmd.verifyIsOfType(PackageType.MAC_PKG); + assertPkgSigned(cmd.outputBundle(), certRequest, + Objects.requireNonNull(keychain.mapCertificateRequests().get(certRequest))); + } + public static void assertSigned(Path path, CertificateRequest certRequest) { assertSigned(path); TKit.assertEquals(certRequest.name(), findCodesignSignOrigin(path).orElse(null), @@ -114,8 +151,8 @@ public final class MacSignVerify { } public static Optional findCodesignSignOrigin(Path path) { - final var exec = Executor.of("/usr/bin/codesign", "--display", "--verbose=4", path.toString()).saveOutput(); - final var result = exec.executeWithoutExitCodeCheck(); + final var exec = Executor.of("/usr/bin/codesign", "--display", "--verbose=4", path.toString()); + final var result = exec.saveOutput().executeWithoutExitCodeCheck(); if (result.getExitCode() == 0) { return Optional.of(result.getOutput().stream().map(line -> { if (line.equals("Signature=adhoc")) { @@ -144,12 +181,34 @@ public final class MacSignVerify { } public static void assertSigned(Path path) { - final var verifier = TKit.TextStreamVerifier.group() - .add(TKit.assertTextStream(": valid on disk").predicate(String::endsWith)) - .add(TKit.assertTextStream(": satisfies its Designated Requirement").predicate(String::endsWith)) - .create(); - verifier.accept(Executor.of("/usr/bin/codesign", "--verify", "--deep", - "--strict", "--verbose=2", path.toString()).executeAndGetOutput().iterator()); + assertSigned(path, false); + } + + private static void assertSigned(Path path, boolean sudo) { + final Executor exec; + if (sudo) { + exec = Executor.of("sudo", "/usr/bin/codesign"); + } else { + exec = Executor.of("/usr/bin/codesign"); + } + exec.addArguments("--verify", "--deep", "--strict", "--verbose=2", path.toString()); + final var result = exec.saveOutput().executeWithoutExitCodeCheck(); + if (result.getExitCode() == 0) { + TKit.TextStreamVerifier.group() + .add(TKit.assertTextStream(": valid on disk").predicate(String::endsWith)) + .add(TKit.assertTextStream(": satisfies its Designated Requirement").predicate(String::endsWith)) + .create().accept(result.getOutput().iterator()); + } else if (!sudo && result.getOutput().stream().findFirst().filter(str -> { + // By some reason /usr/bin/codesign command fails for some installed bundles. + // It is known to fail for some AppContentTest test cases and all FileAssociationsTest test cases. + // Rerunning the command with "sudo" works, though. + return str.equals(String.format("%s: Permission denied", path)); + }).isPresent()) { + TKit.trace("Try /usr/bin/codesign again with `sudo`"); + assertSigned(path, true); + } else { + reportUnexpectedCommandOutcome(exec.getPrintableCommandLine(), result); + } } public static List getPkgCertificateChain(Path path) { diff --git a/test/jdk/tools/jpackage/macosx/MacSignTest.java b/test/jdk/tools/jpackage/macosx/MacSignTest.java index b6f3f91ff58..f5f8b3825cc 100644 --- a/test/jdk/tools/jpackage/macosx/MacSignTest.java +++ b/test/jdk/tools/jpackage/macosx/MacSignTest.java @@ -70,14 +70,14 @@ public class MacSignTest { final List expectedStrings = new ArrayList<>(); expectedStrings.add(JPackageStringBundle.MAIN.cannedFormattedString("message.codesign.failed.reason.app.content")); + expectedStrings.add(JPackageStringBundle.MAIN.cannedFormattedString("error.tool.failed.with.output", "codesign")); + final var xcodeWarning = JPackageStringBundle.MAIN.cannedFormattedString("message.codesign.failed.reason.xcode.tools"); if (!MacHelper.isXcodeDevToolsInstalled()) { expectedStrings.add(xcodeWarning); } - final var keychain = SigningBase.StandardKeychain.EXPIRED.spec().keychain(); - - MacSign.Keychain.withAddedKeychains(List.of(keychain), () -> { + MacSign.withKeychain(keychain -> { // --app-content and --type app-image // Expect `message.codesign.failed.reason.app.content` message in the log. // This is not a fatal error, just a warning. @@ -86,8 +86,6 @@ public class MacSignTest { .ignoreDefaultVerbose(true) .validateOutput(expectedStrings.toArray(CannedFormattedString[]::new)) .addArguments("--app-content", appContent) - .addArguments("--mac-sign") - .addArguments("--mac-signing-keychain", keychain.name()) .addArguments("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN.spec().name()); if (MacHelper.isXcodeDevToolsInstalled()) { @@ -95,8 +93,36 @@ public class MacSignTest { cmd.validateOutput(TKit.assertTextStream(xcodeWarning.getValue()).negate()); } - cmd.execute(1); - }); + MacHelper.useKeychain(cmd, keychain).execute(1); + }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain()); + } + + @Test + public static void testCodesignUnspecifiedFailure() throws IOException { + + var appImageCmd = JPackageCommand.helloAppImage().setFakeRuntime(); + + appImageCmd.executeIgnoreExitCode().assertExitCodeIsZero(); + + // This test expects jpackage to respond in a specific way on a codesign failure. + // The simplest option to trigger codesign failure is to request the signing of an invalid bundle. + // Create app content directory with the name known to fail signing. + final var appContent = appImageCmd.appLayout().contentDirectory().resolve("foo.1"); + Files.createDirectory(appContent); + Files.createFile(appContent.resolve("file")); + + final List expectedStrings = new ArrayList<>(); + expectedStrings.add(JPackageStringBundle.MAIN.cannedFormattedString("error.tool.failed.with.output", "codesign")); + + MacSign.withKeychain(keychain -> { + final var cmd = new JPackageCommand().setPackageType(PackageType.IMAGE) + .ignoreDefaultVerbose(true) + .validateOutput(expectedStrings.toArray(CannedFormattedString[]::new)) + .addArguments("--app-image", appImageCmd.outputBundle()) + .addArguments("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN.spec().name()); + + MacHelper.useKeychain(cmd, keychain).execute(1); + }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain()); } @Test @@ -116,20 +142,16 @@ public class MacSignTest { @Parameter({"MAC_PKG", "EXPIRED_CODESIGN_SIGN_IDENTITY", "GOOD_PKG_SIGN_IDENTITY"}) public static void testExpiredCertificate(PackageType type, SignOption... options) { - final var keychain = SigningBase.StandardKeychain.EXPIRED.spec().keychain(); - - MacSign.Keychain.withAddedKeychains(List.of(keychain), () -> { - final var cmd = JPackageCommand.helloAppImage() + MacSign.withKeychain(keychain -> { + final var cmd = MacHelper.useKeychain(JPackageCommand.helloAppImage(), keychain) .ignoreDefaultVerbose(true) - .addArguments("--mac-sign") - .addArguments("--mac-signing-keychain", keychain.name()) .addArguments(Stream.of(options).map(SignOption::args).flatMap(List::stream).toList()) .setPackageType(type); SignOption.configureOutputValidation(cmd, Stream.of(options).filter(SignOption::expired).toList(), opt -> { return JPackageStringBundle.MAIN.cannedFormattedString("error.certificate.expired", opt.identityName()); }).execute(1); - }); + }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.EXPIRED.keychain()); } @Test @@ -148,39 +170,31 @@ public class MacSignTest { @Parameter({"MAC_PKG", "1", "GOOD_PKG_SIGN_IDENTITY"}) public static void testMultipleCertificates(PackageType type, int jpackageExitCode, SignOption... options) { - final var keychain = SigningBase.StandardKeychain.DUPLICATE.spec().keychain(); - - MacSign.Keychain.withAddedKeychains(List.of(keychain), () -> { - final var cmd = JPackageCommand.helloAppImage() + MacSign.withKeychain(keychain -> { + final var cmd = MacHelper.useKeychain(JPackageCommand.helloAppImage(), keychain) .ignoreDefaultVerbose(true) - .addArguments("--mac-sign") - .addArguments("--mac-signing-keychain", keychain.name()) .addArguments(Stream.of(options).map(SignOption::args).flatMap(List::stream).toList()) .setPackageType(type); SignOption.configureOutputValidation(cmd, List.of(options), opt -> { return JPackageStringBundle.MAIN.cannedFormattedString("error.multiple.certs.found", opt.identityName(), keychain.name()); }).execute(jpackageExitCode); - }); + }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.DUPLICATE.keychain()); } @Test @ParameterSupplier public static void testSelectSigningIdentity(String signingKeyUserName, CertificateRequest certRequest) { - final var keychain = SigningBase.StandardKeychain.MAIN.spec().keychain(); - - MacSign.Keychain.withAddedKeychains(List.of(keychain), () -> { - final var cmd = JPackageCommand.helloAppImage() + MacSign.withKeychain(keychain -> { + final var cmd = MacHelper.useKeychain(JPackageCommand.helloAppImage(), keychain) .setFakeRuntime() - .addArguments("--mac-sign") - .addArguments("--mac-signing-keychain", keychain.name()) .addArguments("--mac-signing-key-user-name", signingKeyUserName); cmd.executeAndAssertHelloAppImageCreated(); MacSignVerify.assertSigned(cmd.outputBundle(), certRequest); - }); + }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain()); } public static Collection testSelectSigningIdentity() { diff --git a/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java b/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java index e93e659408f..37ba8a6c299 100644 --- a/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java @@ -21,12 +21,14 @@ * questions. */ -import java.nio.file.Path; +import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; -import jdk.jpackage.test.JPackageCommand; -import jdk.jpackage.test.Annotations.Test; -import jdk.jpackage.test.Annotations.Parameter; +import java.nio.file.Path; import jdk.jpackage.test.AdditionalLauncher; +import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.Test; +import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.MacSign; /** * Tests generation of app image with --mac-sign and related arguments. Test will @@ -68,13 +70,19 @@ public class SigningAppImageTest { // Unsigned @Parameter({"false", "true", "INVALID_INDEX"}) public void test(boolean doSign, boolean signingKey, SigningBase.CertIndex certEnum) throws Exception { + MacSign.withKeychain(toConsumer(keychain -> { + test(keychain, doSign, signingKey, certEnum); + }), SigningBase.StandardKeychain.MAIN.keychain()); + } + + private void test(MacSign.ResolvedKeychain keychain, boolean doSign, boolean signingKey, SigningBase.CertIndex certEnum) throws Exception { final var certIndex = certEnum.value(); JPackageCommand cmd = JPackageCommand.helloAppImage(); if (doSign) { cmd.addArguments("--mac-sign", "--mac-signing-keychain", - SigningBase.getKeyChain()); + keychain.name()); if (signingKey) { cmd.addArguments("--mac-signing-key-user-name", SigningBase.getDevName(certIndex)); diff --git a/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java b/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java index 94199b31434..906734e6a9c 100644 --- a/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 @@ -21,14 +21,16 @@ * questions. */ -import java.nio.file.Path; +import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; -import jdk.jpackage.test.JPackageCommand; -import jdk.jpackage.test.TKit; -import jdk.jpackage.test.PackageType; -import jdk.jpackage.test.Annotations.Test; -import jdk.jpackage.test.Annotations.Parameter; +import java.nio.file.Path; import jdk.jpackage.test.AdditionalLauncher; +import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.Test; +import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.PackageType; +import jdk.jpackage.test.TKit; /** * Tests generation of app image and then signs generated app image with --mac-sign @@ -67,6 +69,12 @@ public class SigningAppImageTwoStepsTest { // Unsigned @Parameter({"false", "true"}) public void test(boolean signAppImage, boolean signingKey) throws Exception { + MacSign.withKeychain(toConsumer(keychain -> { + test(keychain, signAppImage, signingKey); + }), SigningBase.StandardKeychain.MAIN.keychain()); + } + + private static void test(MacSign.ResolvedKeychain keychain, boolean signAppImage, boolean signingKey) throws Exception { Path appimageOutput = TKit.createTempDirectory("appimage"); @@ -78,7 +86,7 @@ public class SigningAppImageTwoStepsTest { if (signAppImage) { appImageCmd.addArguments("--mac-sign", "--mac-signing-keychain", - SigningBase.getKeyChain()); + keychain.name()); if (signingKey) { appImageCmd.addArguments("--mac-signing-key-user-name", SigningBase.getDevName(SigningBase.DEFAULT_INDEX)); @@ -103,7 +111,7 @@ public class SigningAppImageTwoStepsTest { cmd.setPackageType(PackageType.IMAGE) .addArguments("--app-image", appImageCmd.outputBundle().toAbsolutePath()) .addArguments("--mac-sign") - .addArguments("--mac-signing-keychain", SigningBase.getKeyChain()); + .addArguments("--mac-signing-keychain", keychain.name()); if (signingKey) { cmd.addArguments("--mac-signing-key-user-name", SigningBase.getDevName(SigningBase.DEFAULT_INDEX)); diff --git a/test/jdk/tools/jpackage/macosx/SigningPackageFromTwoStepAppImageTest.java b/test/jdk/tools/jpackage/macosx/SigningPackageFromTwoStepAppImageTest.java index d25d9a7fa81..6db1cb2faab 100644 --- a/test/jdk/tools/jpackage/macosx/SigningPackageFromTwoStepAppImageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningPackageFromTwoStepAppImageTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 @@ -21,15 +21,18 @@ * questions. */ +import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; + import java.nio.file.Path; +import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.ApplicationLayout; import jdk.jpackage.test.JPackageCommand; -import jdk.jpackage.test.TKit; +import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacSign; 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; +import jdk.jpackage.test.TKit; /** * Tests generation of dmg and pkg from signed predefined app image which was @@ -102,6 +105,12 @@ public class SigningPackageFromTwoStepAppImageTest { // Unsigned @Parameter({"false", "true"}) public void test(boolean signAppImage, boolean signingKey) throws Exception { + MacSign.withKeychain(toConsumer(keychain -> { + test(keychain, signAppImage, signingKey); + }), SigningBase.StandardKeychain.MAIN.keychain()); + } + + private void test(MacSign.ResolvedKeychain keychain, boolean signAppImage, boolean signingKey) throws Exception { Path appimageOutput = TKit.createTempDirectory("appimage"); @@ -112,7 +121,7 @@ public class SigningPackageFromTwoStepAppImageTest { .setArgumentValue("--dest", appimageOutput); if (signAppImage) { appImageCmd.addArguments("--mac-sign", - "--mac-signing-keychain", SigningBase.getKeyChain()); + "--mac-signing-keychain", keychain.name()); if (signingKey) { appImageCmd.addArguments("--mac-signing-key-user-name", SigningBase.getDevName(SigningBase.DEFAULT_INDEX)); @@ -133,7 +142,7 @@ public class SigningPackageFromTwoStepAppImageTest { appImageSignedCmd.setPackageType(PackageType.IMAGE) .addArguments("--app-image", appImageCmd.outputBundle().toAbsolutePath()) .addArguments("--mac-sign") - .addArguments("--mac-signing-keychain", SigningBase.getKeyChain()); + .addArguments("--mac-signing-keychain", keychain.name()); if (signingKey) { appImageSignedCmd.addArguments("--mac-signing-key-user-name", SigningBase.getDevName(SigningBase.DEFAULT_INDEX)); @@ -154,7 +163,7 @@ public class SigningPackageFromTwoStepAppImageTest { if (signAppImage) { cmd.addArguments("--mac-sign", "--mac-signing-keychain", - SigningBase.getKeyChain()); + keychain.name()); if (signingKey) { cmd.addArguments("--mac-signing-key-user-name", SigningBase.getDevName(SigningBase.DEFAULT_INDEX)); diff --git a/test/jdk/tools/jpackage/macosx/SigningPackageTest.java b/test/jdk/tools/jpackage/macosx/SigningPackageTest.java index 2c2f5c3bb0f..b1e9155dacb 100644 --- a/test/jdk/tools/jpackage/macosx/SigningPackageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningPackageTest.java @@ -21,14 +21,17 @@ * questions. */ +import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; + import java.nio.file.Path; +import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.ApplicationLayout; import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacSign; 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 with --mac-sign and related arguments. @@ -144,6 +147,12 @@ public class SigningPackageTest { // Signing-indentity, but sign pkg only and UNICODE certificate @Parameter({"false", "false", "true", "UNICODE_INDEX"}) public static void test(boolean signingKey, boolean signAppImage, boolean signPKG, SigningBase.CertIndex certEnum) throws Exception { + MacSign.withKeychain(toConsumer(keychain -> { + test(keychain, signingKey, signAppImage, signPKG, certEnum); + }), SigningBase.StandardKeychain.MAIN.keychain()); + } + + private static void test(MacSign.ResolvedKeychain keychain, boolean signingKey, boolean signAppImage, boolean signPKG, SigningBase.CertIndex certEnum) throws Exception { final var certIndex = certEnum.value(); new PackageTest() @@ -151,7 +160,7 @@ public class SigningPackageTest { .forTypes(PackageType.MAC) .addInitializer(cmd -> { cmd.addArguments("--mac-sign", - "--mac-signing-keychain", SigningBase.getKeyChain()); + "--mac-signing-keychain", keychain.name()); if (signingKey) { cmd.addArguments("--mac-signing-key-user-name", SigningBase.getDevName(certIndex)); diff --git a/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java b/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java index 3522d8d43e5..16cf616cfd3 100644 --- a/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 @@ -21,31 +21,39 @@ * questions. */ +import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; + import java.nio.file.Path; -import jdk.jpackage.test.ApplicationLayout; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Stream; +import jdk.jpackage.test.Annotations.ParameterSupplier; +import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.JPackageCommand; -import jdk.jpackage.test.TKit; +import jdk.jpackage.test.JPackageStringBundle; +import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSignVerify; +import jdk.jpackage.test.PackageFile; 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; +import jdk.jpackage.test.TKit; /** - * 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 system property - * "jpackage.mac.signing.key.user.name", and - * "jpackagerTest" can be over-ridden by system property - * "jpackage.mac.signing.keychain" + * Tests packaging of a signed/unsigned predefined app image into a + * signed/unsigned .pkg or .dmg package. + * + *

+ * Prerequisites: A keychain with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN}. */ /* @@ -64,100 +72,180 @@ import jdk.jpackage.test.Annotations.Parameter; */ 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, true, SigningBase.DEFAULT_INDEX); - SigningBase.verifySpctl(outputBundle, "install", SigningBase.DEFAULT_INDEX); - } - - private static void verifyDMG(JPackageCommand cmd) { - // DMG always unsigned, so we will check it - Path outputBundle = cmd.outputBundle(); - SigningBase.verifyDMG(outputBundle); - } - - 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.DEFAULT_INDEX); - SigningBase.verifyCodesign(dmgImage, isSigned, SigningBase.DEFAULT_INDEX); - if (isSigned) { - SigningBase.verifySpctl(dmgImage, "exec", SigningBase.DEFAULT_INDEX); - } - } - }); - } - @Test - // (Signed, "signing-key or sign-identity"}) - // Signed and signing-key - @Parameter({"true", "true"}) - // Signed and signing-identity - @Parameter({"true", "false"}) - // Unsigned - @Parameter({"false", "true"}) - public static void test(boolean signAppImage, boolean signingKey) throws Exception { - Path appimageOutput = TKit.createTempDirectory("appimage"); + @ParameterSupplier + public static void test(TestSpec spec) { + MacSign.withKeychain(toConsumer(keychain -> { + spec.test(keychain); + }), SigningBase.StandardKeychain.MAIN.keychain()); + } - JPackageCommand appImageCmd = JPackageCommand.helloAppImage() - .setArgumentValue("--dest", appimageOutput); - if (signAppImage) { - appImageCmd.addArguments("--mac-sign") - .addArguments("--mac-signing-keychain", - SigningBase.getKeyChain()); - if (signingKey) { - appImageCmd.addArguments("--mac-signing-key-user-name", - SigningBase.getDevName(SigningBase.DEFAULT_INDEX)); - } else { - appImageCmd.addArguments("--mac-app-image-sign-identity", - SigningBase.getAppCert(SigningBase.DEFAULT_INDEX)); + public record TestSpec(Optional signAppImage, Map signPackage) { + + public TestSpec { + Objects.requireNonNull(signAppImage); + Objects.requireNonNull(signPackage); + + if ((signAppImage.isEmpty() && signPackage.isEmpty()) || !PackageType.MAC.containsAll(signPackage.keySet())) { + // Unexpected package types. + throw new IllegalArgumentException(); + } + + // Ensure stable result of toString() call. + if (!SortedMap.class.isInstance(signPackage)) { + signPackage = new TreeMap<>(signPackage); } } - 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-keychain", - SigningBase.getKeyChain()); - if (signingKey) { - cmd.addArguments("--mac-signing-key-user-name", - SigningBase.getDevName(SigningBase.DEFAULT_INDEX)); - } else { - cmd.addArguments("--mac-installer-sign-identity", - SigningBase.getInstallerCert(SigningBase.DEFAULT_INDEX)); - } - } - }) - .forTypes(PackageType.MAC_PKG) - .addBundleVerifier(SigningPackageTwoStepTest::verifyPKG) - .forTypes(PackageType.MAC_DMG) - .addInitializer(cmd -> { - if (signAppImage && !signingKey) { - // jpackage throws expected error with - // --mac-installer-sign-identity and DMG type - cmd.removeArgumentWithValue("--mac-installer-sign-identity"); - // It will do nothing, but it signals test that app - // image itself is signed for verification. - cmd.addArguments("--mac-app-image-sign-identity", - SigningBase.getAppCert(SigningBase.DEFAULT_INDEX)); - } - }) - .addBundleVerifier(SigningPackageTwoStepTest::verifyDMG) - .addBundleVerifier(SigningPackageTwoStepTest::verifyAppImageInDMG) - .run(); + @Override + public String toString() { + var sb = new StringBuilder(); + + signAppImage.ifPresent(signOption -> { + sb.append(String.format("app-image=%s", signOption)); + }); + + if (!sb.isEmpty() && !signPackage.isEmpty()) { + sb.append("; "); + } + + if (!signPackage.isEmpty()) { + sb.append(signPackage); + } + + return sb.toString(); + } + + boolean signNativeBundle() { + return signPackage.isEmpty(); + } + + static Builder build() { + return new Builder(); + } + + static class Builder { + + TestSpec create() { + return new TestSpec(Optional.ofNullable(signAppImage), signPackage); + } + + Builder certRequest(SigningBase.StandardCertificateRequest v) { + return certRequest(v.spec()); + } + + Builder certRequest(MacSign.CertificateRequest v) { + certRequest = Objects.requireNonNull(v); + return this; + } + + Builder signIdentityType(SignKeyOption.Type v) { + signIdentityType = Objects.requireNonNull(v); + return this; + } + + Builder signAppImage() { + signAppImage = createSignKeyOption(); + return this; + } + + Builder signPackage(PackageType type) { + Objects.requireNonNull(type); + signPackage.put(type, createSignKeyOption()); + return this; + } + + Builder signPackage() { + PackageType.MAC.forEach(this::signPackage); + return this; + } + + private SignKeyOption createSignKeyOption() { + return new SignKeyOption(signIdentityType, certRequest); + } + + private MacSign.CertificateRequest certRequest = SigningBase.StandardCertificateRequest.CODESIGN.spec(); + private SignKeyOption.Type signIdentityType = SignKeyOption.Type.SIGN_KEY_IDENTITY; + + private SignKeyOption signAppImage; + private Map signPackage = new HashMap<>(); + } + + void test(MacSign.ResolvedKeychain keychain) { + + var appImageCmd = JPackageCommand.helloAppImage().setFakeRuntime(); + MacHelper.useKeychain(appImageCmd, keychain); + signAppImage.ifPresent(signOption -> { + signOption.setTo(appImageCmd); + }); + + var test = new PackageTest(); + + signAppImage.map(SignKeyOption::certRequest).ifPresent(certRequest -> { + // The predefined app image is signed, verify bundled app image is signed too. + test.addInstallVerifier(cmd -> { + MacSignVerify.verifyAppImageSigned(cmd, certRequest, keychain); + }); + }); + + Optional.ofNullable(signPackage.get(PackageType.MAC_PKG)).map(SignKeyOption::certRequest).ifPresent(certRequest -> { + test.forTypes(PackageType.MAC_PKG, () -> { + test.addBundleVerifier(cmd -> { + MacSignVerify.verifyPkgSigned(cmd, certRequest, keychain); + }); + }); + }); + + test.forTypes(signPackage.keySet()).addRunOnceInitializer(() -> { + appImageCmd.setArgumentValue("--dest", TKit.createTempDirectory("appimage")).execute(0); + }).addInitializer(cmd -> { + MacHelper.useKeychain(cmd, keychain); + cmd.addArguments("--app-image", appImageCmd.outputBundle()); + cmd.removeArgumentWithValue("--input"); + Optional.ofNullable(signPackage.get(cmd.packageType())).ifPresent(signOption -> { + signOption.setTo(cmd); + }); + + if (signAppImage.isPresent()) { + // Predefined app image is signed. Expect a warning. + cmd.validateOutput(JPackageStringBundle.MAIN.cannedFormattedString( + "warning.per.user.app.image.signed", + PackageFile.getPathInAppImage(Path.of("")))); + } else if (cmd.packageType() == PackageType.MAC_PKG && signPackage.containsKey(cmd.packageType())) { + // Create signed ".pkg" bundle from the unsigned predefined app image. Expect a warning. + cmd.validateOutput(JPackageStringBundle.MAIN.cannedFormattedString("warning.unsigned.app.image", "pkg")); + } + }) + .run(); + } + } + + public static Collection test() { + + List data = new ArrayList<>(); + + Stream.of(SignKeyOption.Type.values()).flatMap(signIdentityType -> { + return Stream.of( + // Sign both predefined app image and native package. + TestSpec.build().signIdentityType(signIdentityType) + .signAppImage() + .signPackage() + .certRequest(SigningBase.StandardCertificateRequest.PKG) + .signPackage(PackageType.MAC_PKG), + + // Don't sign predefined app image, sign native package. + TestSpec.build().signIdentityType(signIdentityType) + .signPackage() + .certRequest(SigningBase.StandardCertificateRequest.PKG) + .signPackage(PackageType.MAC_PKG), + + // Sign predefined app image, don't sign native package. + TestSpec.build().signIdentityType(signIdentityType).signAppImage() + ); + }).forEach(data::add); + + return data.stream().map(TestSpec.Builder::create).map(v -> { + return new Object[] {v}; + }).toList(); } } diff --git a/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java b/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java index b137824a910..efcaadc3fa8 100644 --- a/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java @@ -21,51 +21,53 @@ * questions. */ +import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; + import java.io.IOException; import java.nio.file.Path; import java.util.function.Predicate; import java.util.stream.Stream; - import jdk.jpackage.test.Annotations.Parameter; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.Executor; import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacSign; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; import jdk.jpackage.test.TKit; /** - * Tests generation of dmg and pkg with --mac-sign and related arguments. - * Test will generate pkg and verifies its signature. It verifies that dmg - * is not signed, but runtime image inside dmg is signed. + * Tests generation of dmg and pkg with --mac-sign and related arguments. Test + * will generate pkg and verifies its signature. It verifies that dmg is not + * signed, but runtime image inside dmg is signed. * - * Note: Specific UNICODE signing is not tested, since it is shared code - * with app image signing and it will be covered by SigningPackageTest. + *

+ * Note: Specific UNICODE signing is not tested, since it is shared code with + * app image signing and it will be covered by SigningPackageTest. * + *

* Following combinations are tested: - * 1) "--runtime-image" points to unsigned JDK bundle and --mac-sign is not + *

    + *
  1. "--runtime-image" points to unsigned JDK bundle and --mac-sign is not * provided. Expected result: runtime image ad-hoc signed. - * 2) "--runtime-image" points to unsigned JDK bundle and --mac-sign is + *
  2. "--runtime-image" points to unsigned JDK bundle and --mac-sign is * provided. Expected result: Everything is signed with provided certificate. - * 3) "--runtime-image" points to signed JDK bundle and --mac-sign is not + *
  3. "--runtime-image" points to signed JDK bundle and --mac-sign is not * provided. Expected result: runtime image is signed with original certificate. - * 4) "--runtime-image" points to signed JDK bundle and --mac-sign is provided. + *
  4. "--runtime-image" points to signed JDK bundle and --mac-sign is provided. * Expected result: runtime image is signed with provided certificate. - * 5) "--runtime-image" points to JDK image and --mac-sign is not provided. + *
  5. "--runtime-image" points to JDK image and --mac-sign is not provided. * Expected result: runtime image ad-hoc signed. - * 6) "--runtime-image" points to JDK image and --mac-sign is provided. + *
  6. "--runtime-image" points to JDK image and --mac-sign is provided. * Expected result: Everything is signed with provided certificate. + *
* * 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: + * "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 system property - * "jpackage.mac.signing.key.user.name", and - * "jpackagerTest" can be over-ridden by system property - * "jpackage.mac.signing.keychain" + * "jpackage.mac.signing.key.user.name" */ /* @@ -84,17 +86,17 @@ import jdk.jpackage.test.TKit; */ public class SigningRuntimeImagePackageTest { - private static JPackageCommand addSignOptions(JPackageCommand cmd, int certIndex) { + private static JPackageCommand addSignOptions(JPackageCommand cmd, MacSign.ResolvedKeychain keychain, int certIndex) { if (certIndex != SigningBase.CertIndex.INVALID_INDEX.value()) { cmd.addArguments( "--mac-sign", - "--mac-signing-keychain", SigningBase.getKeyChain(), + "--mac-signing-keychain", keychain.name(), "--mac-signing-key-user-name", SigningBase.getDevName(certIndex)); } return cmd; } - private static Path createInputRuntimeBundle(int certIndex) throws IOException { + private static Path createInputRuntimeBundle(MacSign.ResolvedKeychain keychain, int certIndex) throws IOException { final var runtimeImage = JPackageCommand.createInputRuntimeImage(); @@ -111,7 +113,7 @@ public class SigningRuntimeImagePackageTest { .addArguments("--runtime-image", runtimeImage) .addArguments("--dest", runtimeBundleWorkDir); - addSignOptions(cmd, certIndex); + addSignOptions(cmd, keychain, certIndex); cmd.execute(); @@ -147,13 +149,21 @@ public class SigningRuntimeImagePackageTest { public static void test(boolean useJDKBundle, SigningBase.CertIndex jdkBundleCert, SigningBase.CertIndex signCert) throws Exception { + MacSign.withKeychain(toConsumer(keychain -> { + test(keychain, useJDKBundle, jdkBundleCert, signCert); + }), SigningBase.StandardKeychain.MAIN.keychain()); + } + + private static void test(MacSign.ResolvedKeychain keychain, boolean useJDKBundle, + SigningBase.CertIndex jdkBundleCert, + SigningBase.CertIndex signCert) throws Exception { final Path inputRuntime[] = new Path[1]; new PackageTest() .addRunOnceInitializer(() -> { if (useJDKBundle) { - inputRuntime[0] = createInputRuntimeBundle(jdkBundleCert.value()); + inputRuntime[0] = createInputRuntimeBundle(keychain, jdkBundleCert.value()); } else { inputRuntime[0] = JPackageCommand.createInputRuntimeImage(); } @@ -164,7 +174,7 @@ public class SigningRuntimeImagePackageTest { // create input directory in the test and jpackage fails // if --input references non existent directory. cmd.removeArgumentWithValue("--input"); - addSignOptions(cmd, signCert.value()); + addSignOptions(cmd, keychain, signCert.value()); }) .addInstallVerifier(cmd -> { final var certIndex = Stream.of(signCert, jdkBundleCert) diff --git a/test/jdk/tools/jpackage/macosx/base/SigningBase.java b/test/jdk/tools/jpackage/macosx/base/SigningBase.java index 5484245f111..1e38f9b0c29 100644 --- a/test/jdk/tools/jpackage/macosx/base/SigningBase.java +++ b/test/jdk/tools/jpackage/macosx/base/SigningBase.java @@ -90,17 +90,29 @@ public class SigningBase { private final CertificateRequest spec; } + /** + * Standard keychains used in signing tests. + */ public enum StandardKeychain { - MAIN(DEFAULT_KEYCHAIN, + /** + * The primary keychain with good certificates. + */ + MAIN("jpackagerTest.keychain", StandardCertificateRequest.CODESIGN, StandardCertificateRequest.PKG, StandardCertificateRequest.CODESIGN_UNICODE, StandardCertificateRequest.PKG_UNICODE), + /** + * A keychain with some good and some expired certificates. + */ EXPIRED("jpackagerTest-expired.keychain", StandardCertificateRequest.CODESIGN, StandardCertificateRequest.PKG, StandardCertificateRequest.CODESIGN_EXPIRED, StandardCertificateRequest.PKG_EXPIRED), + /** + * A keychain with duplicated certificates. + */ DUPLICATE("jpackagerTest-duplicate.keychain", StandardCertificateRequest.CODESIGN, StandardCertificateRequest.PKG, @@ -114,30 +126,26 @@ public class SigningBase { StandardKeychain(String keychainName, CertificateRequest cert, CertificateRequest... otherCerts) { final var builder = keychain(keychainName).addCert(cert); List.of(otherCerts).forEach(builder::addCert); - this.spec = new ResolvedKeychain(builder.create()); + this.keychain = new ResolvedKeychain(builder.create()); } - public KeychainWithCertsSpec spec() { - return spec.spec(); + public ResolvedKeychain keychain() { + return keychain; } public X509Certificate mapCertificateRequest(CertificateRequest certRequest) { - return Objects.requireNonNull(spec.mapCertificateRequests().get(certRequest)); + return Objects.requireNonNull(keychain.mapCertificateRequests().get(certRequest)); } private static KeychainWithCertsSpec.Builder keychain(String name) { return new KeychainWithCertsSpec.Builder().name(name); } - private static CertificateRequest.Builder cert() { - return new CertificateRequest.Builder(); - } - private static List signingEnv() { - return Stream.of(values()).map(StandardKeychain::spec).toList(); + return Stream.of(values()).map(StandardKeychain::keychain).map(ResolvedKeychain::spec).toList(); } - private final ResolvedKeychain spec; + private final ResolvedKeychain keychain; } public static void setUp() { @@ -179,7 +187,6 @@ public class SigningBase { "jpackage.openjdk.java.net", "jpackage.openjdk.java.net (รถ)", }; - private static String DEFAULT_KEYCHAIN = "jpackagerTest.keychain"; public static String getDevName(int certIndex) { // Always use values from system properties if set @@ -195,16 +202,6 @@ public class SigningBase { return Arrays.binarySearch(DEV_NAMES, devName); } - // Returns 'true' if dev name from DEV_NAMES - public static boolean isDevNameDefault() { - String value = System.getProperty("jpackage.mac.signing.key.user.name"); - if (value != null) { - return false; - } - - return true; - } - public static String getAppCert(int certIndex) { return "Developer ID Application: " + getDevName(certIndex); } @@ -213,16 +210,6 @@ public class SigningBase { return "Developer ID Installer: " + getDevName(certIndex); } - public static String getKeyChain() { - // Always use values from system properties if set - String value = System.getProperty("jpackage.mac.signing.keychain"); - if (value != null) { - return value; - } - - return DEFAULT_KEYCHAIN; - } - public static void verifyCodesign(Path target, boolean signed, int certIndex) { if (signed) { final var certRequest = getCertRequest(certIndex);