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 7874df3fd69..f81c35cea0b 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -294,34 +294,8 @@ public class JPackageCommand extends CommandArguments { public JPackageCommand setFakeRuntime() { verifyMutable(); - - ThrowingConsumer createBulkFile = path -> { - Files.createDirectories(path.getParent()); - try (FileOutputStream out = new FileOutputStream(path.toFile())) { - byte[] bytes = new byte[4 * 1024]; - new SecureRandom().nextBytes(bytes); - out.write(bytes); - } - }; - addPrerequisiteAction(cmd -> { - Path fakeRuntimeDir = TKit.createTempDirectory("fake_runtime"); - - TKit.trace(String.format("Init fake runtime in [%s] directory", - fakeRuntimeDir)); - - if (TKit.isOSX()) { - // Make MacAppImageBuilder happy - createBulkFile.accept(fakeRuntimeDir.resolve(Path.of( - "lib/jli/libjli.dylib"))); - } - - // Make sure fake runtime takes some disk space. - // Package bundles with 0KB size are unexpected and considered - // an error by PackageTest. - createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("lib", "bulk"))); - - cmd.setArgumentValue("--runtime-image", fakeRuntimeDir); + cmd.setArgumentValue("--runtime-image", createInputRuntimeImage(RuntimeImageType.RUNTIME_TYPE_FAKE)); }); return this; @@ -390,24 +364,77 @@ public class JPackageCommand extends CommandArguments { return cmd; } + public enum RuntimeImageType { + + /** + * Runtime suitable for running the default "Hello" test app. + */ + RUNTIME_TYPE_HELLO_APP, + + /** + * Fake runtime. + */ + RUNTIME_TYPE_FAKE, + + ; + } + public static Path createInputRuntimeImage() { + return createInputRuntimeImage(RuntimeImageType.RUNTIME_TYPE_HELLO_APP); + } + + public static Path createInputRuntimeImage(RuntimeImageType role) { + Objects.requireNonNull(role); final Path runtimeImageDir; + switch (role) { - if (JPackageCommand.DEFAULT_RUNTIME_IMAGE != null) { - runtimeImageDir = JPackageCommand.DEFAULT_RUNTIME_IMAGE; - } else { - runtimeImageDir = TKit.createTempDirectory("runtime-image").resolve("data"); + case RUNTIME_TYPE_FAKE -> { + Consumer createBulkFile = ThrowingConsumer.toConsumer(path -> { + Files.createDirectories(path.getParent()); + try (FileOutputStream out = new FileOutputStream(path.toFile())) { + byte[] bytes = new byte[4 * 1024]; + new SecureRandom().nextBytes(bytes); + out.write(bytes); + } + }); - new Executor().setToolProvider(JavaTool.JLINK) - .dumpOutput() - .addArguments( - "--output", runtimeImageDir.toString(), - "--add-modules", "java.desktop", - "--strip-debug", - "--no-header-files", - "--no-man-pages") - .execute(); + runtimeImageDir = TKit.createTempDirectory("fake_runtime"); + + TKit.trace(String.format("Init fake runtime in [%s] directory", runtimeImageDir)); + + if (TKit.isOSX()) { + // Make MacAppImageBuilder happy + createBulkFile.accept(runtimeImageDir.resolve(Path.of("lib/jli/libjli.dylib"))); + } + + // Make sure fake runtime takes some disk space. + // Package bundles with 0KB size are unexpected and considered + // an error by PackageTest. + createBulkFile.accept(runtimeImageDir.resolve(Path.of("lib", "bulk"))); + } + + case RUNTIME_TYPE_HELLO_APP -> { + if (JPackageCommand.DEFAULT_RUNTIME_IMAGE != null && !isFakeRuntime(DEFAULT_RUNTIME_IMAGE)) { + runtimeImageDir = JPackageCommand.DEFAULT_RUNTIME_IMAGE; + } else { + runtimeImageDir = TKit.createTempDirectory("runtime-image").resolve("data"); + + new Executor().setToolProvider(JavaTool.JLINK) + .dumpOutput() + .addArguments( + "--output", runtimeImageDir.toString(), + "--add-modules", "java.desktop", + "--strip-debug", + "--no-header-files", + "--no-man-pages") + .execute(); + } + } + + default -> { + throw ExceptionBox.reachedUnreachable(); + } } return runtimeImageDir; 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 1cb5532d46a..dc1a7b3512b 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java @@ -46,6 +46,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -66,6 +67,7 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; +import jdk.jpackage.internal.util.Enquoter; import jdk.jpackage.internal.util.FileUtils; import jdk.jpackage.internal.util.MacBundle; import jdk.jpackage.internal.util.PListReader; @@ -75,6 +77,8 @@ 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.MacSign.CertificateType; +import jdk.jpackage.test.MacSign.ResolvedKeychain; import jdk.jpackage.test.PackageTest.PackageHandlers; import jdk.jpackage.test.RunnablePackageTest.Action; import org.xml.sax.SAXException; @@ -430,18 +434,50 @@ public final class MacHelper { } } + public static final class RuntimeBundleBuilder { + + public Path create() { + return createRuntimeBundle(type, Optional.ofNullable(mutator)); + } + + public RuntimeBundleBuilder type(JPackageCommand.RuntimeImageType v) { + type = Objects.requireNonNull(v); + return this; + } + + public RuntimeBundleBuilder mutator(Consumer v) { + mutator = v; + return this; + } + + public RuntimeBundleBuilder mutate(Consumer mutator) { + mutator.accept(this); + return this; + } + + private RuntimeBundleBuilder() { + } + + private JPackageCommand.RuntimeImageType type = JPackageCommand.RuntimeImageType.RUNTIME_TYPE_HELLO_APP; + private Consumer mutator; + }; + + public static RuntimeBundleBuilder buildRuntimeBundle() { + return new RuntimeBundleBuilder(); + } + public static Path createRuntimeBundle(Consumer mutator) { - return createRuntimeBundle(Optional.of(mutator)); + return buildRuntimeBundle().mutator(Objects.requireNonNull(mutator)).create(); } public static Path createRuntimeBundle() { - return createRuntimeBundle(Optional.empty()); + return buildRuntimeBundle().create(); } - public static Path createRuntimeBundle(Optional> mutator) { + private static Path createRuntimeBundle(JPackageCommand.RuntimeImageType type, Optional> mutator) { Objects.requireNonNull(mutator); - final var runtimeImage = JPackageCommand.createInputRuntimeImage(); + final var runtimeImage = JPackageCommand.createInputRuntimeImage(type); final var runtimeBundleWorkDir = TKit.createTempDirectory("runtime-bundle"); @@ -495,25 +531,244 @@ public final class MacHelper { return cmd; } - public record SignKeyOption(Type type, CertificateRequest certRequest) { + public static final class ResolvableCertificateRequest { + + public ResolvableCertificateRequest( + CertificateRequest certRequest, + Function certResolver, + String label) { + + Objects.requireNonNull(certRequest); + Objects.requireNonNull(certResolver); + Objects.requireNonNull(label); + if (label.isBlank()) { + throw new IllegalArgumentException(); + } + + this.certRequest = certRequest; + this.certResolver = certResolver; + this.label = label; + } + + public ResolvableCertificateRequest( + CertificateRequest certRequest, + ResolvedKeychain keychain, + String label) { + this(certRequest, keychain.asCertificateResolver(), label); + } + + @Override + public String toString() { + return label; + } + + public CertificateRequest certRequest() { + return certRequest; + } + + public X509Certificate cert() { + return certResolver.apply(certRequest); + } + + public CertificateType type() { + return certRequest.type(); + } + + public String name() { + return certRequest.name(); + } + + public String shortName() { + return certRequest.shortName(); + } + + public int days() { + return certRequest.days(); + } + + public boolean expired() { + return certRequest.expired(); + } + + public boolean trusted() { + return certRequest.trusted(); + } + + private final CertificateRequest certRequest; + private final Function certResolver; + private final String label; + } + + public interface NamedCertificateRequestSupplier { + + String name(); + + CertificateRequest certRequest(); + + default ResolvableCertificateRequest certRequest(ResolvedKeychain keychain) { + Objects.requireNonNull(keychain); + var certRequest = Objects.requireNonNull(certRequest()); + if (keychain.spec().certificateRequests().contains(certRequest)) { + return new ResolvableCertificateRequest(certRequest, keychain.asCertificateResolver(), name()); + } else { + throw new IllegalArgumentException(String.format( + "Certificate request %s not found in [%s] keychain", + name(), keychain.spec().keychain().name())); + } + } + } + + public record SignKeyOption(Type type, ResolvableCertificateRequest certRequest, Optional customOptionValue) { public SignKeyOption { Objects.requireNonNull(type); Objects.requireNonNull(certRequest); + Objects.requireNonNull(customOptionValue); + if (customOptionValue.isEmpty() == (type == Type.SIGN_KEY_USER_NAME)) { + throw new IllegalArgumentException(); + } + } + + public SignKeyOption(Type type, ResolvableCertificateRequest certRequest) { + this(type, certRequest, Optional.empty()); + } + + public SignKeyOption( + Type type, + NamedCertificateRequestSupplier certRequestSupplier, + ResolvedKeychain keychain) { + + this(type, certRequestSupplier.certRequest(keychain)); + } + + public SignKeyOption(String optionValue, ResolvableCertificateRequest certRequest) { + this(Type.SIGN_KEY_USER_NAME, certRequest, Optional.of(optionValue)); + } + + public SignKeyOption( + String optionValue, + NamedCertificateRequestSupplier certRequestSupplier, + ResolvedKeychain keychain) { + + this(optionValue, certRequestSupplier.certRequest(keychain)); + } + + public enum Name { + KEY_USER_NAME("--mac-signing-key-user-name"), + KEY_IDENTITY_APP_IMAGE("--mac-app-image-sign-identity"), + KEY_IDENTITY_INSTALLER("--mac-installer-sign-identity"), + ; + + Name(String optionName) { + this.optionName = Objects.requireNonNull(optionName); + } + + public String optionName() { + return optionName; + } + + public boolean passThrough() { + return this != KEY_USER_NAME; + } + + private final String optionName; } public enum Type { - SIGN_KEY_USER_NAME, - SIGN_KEY_IDENTITY, + /** + * "--mac-signing-key-user-name" option with custom value + */ + SIGN_KEY_USER_NAME(Name.KEY_USER_NAME), + + /** + * "--mac-signing-key-user-name" option with the short user name, e.g.: + * {@code --mac-signing-key-user-name foo} + */ + SIGN_KEY_USER_SHORT_NAME(Name.KEY_USER_NAME), + + /** + * "--mac-signing-key-user-name" option with the full user name (aka signing + * identity name), e.g.: + * {@code --mac-signing-key-user-name 'Developer ID Application: foo'} + */ + SIGN_KEY_USER_FULL_NAME(Name.KEY_USER_NAME), + + /** + * "--mac-installer-sign-identity" or "--mac-app-image-sign-identity" option + * with the signing identity name, e.g.: + * {@code --mac-app-image-sign-identity 'Developer ID Application: foo'} + */ + SIGN_KEY_IDENTITY(Map.of( + MacSign.CertificateType.CODE_SIGN, Name.KEY_IDENTITY_APP_IMAGE, + MacSign.CertificateType.INSTALLER, Name.KEY_IDENTITY_INSTALLER)), + + /** + * "--mac-app-image-sign-identity" regardless of the type of signing identity + * (for signing app image or .pkg installer). + */ + SIGN_KEY_IDENTITY_APP_IMAGE(Name.KEY_IDENTITY_APP_IMAGE), + + /** + * "--mac-installer-sign-identity" regardless of the type of signing identity + * (for signing app image or .pkg installer). + */ + SIGN_KEY_IDENTITY_INSTALLER(Name.KEY_IDENTITY_INSTALLER), + ; + + Type(Map optionNameMap) { + Objects.requireNonNull(optionNameMap); + this.optionNameMapper = certType -> { + return Optional.of(optionNameMap.get(certType)); + }; + } + + Type(Name optionName) { + Objects.requireNonNull(optionName); + this.optionNameMapper = _ -> Optional.of(optionName); + } + + Type() { + this.optionNameMapper = _ -> Optional.empty(); + } + + public Optional mapOptionName(MacSign.CertificateType certType) { + return optionNameMapper.apply(Objects.requireNonNull(certType)); + } + + public static Type[] defaultValues() { + return new Type[] { + SIGN_KEY_USER_SHORT_NAME, + SIGN_KEY_USER_FULL_NAME, + SIGN_KEY_IDENTITY + }; + } + + private final Function> optionNameMapper; } @Override public String toString() { var sb = new StringBuilder(); + sb.append('{'); applyTo((optionName, _) -> { - sb.append(String.format("{%s: %s}", optionName, certRequest)); + sb.append(optionName); + switch (type) { + case SIGN_KEY_USER_FULL_NAME -> { + sb.append("/full"); + } + case SIGN_KEY_USER_NAME -> { + customOptionValue.ifPresent(optionValue -> { + sb.append("=").append(ENQUOTER.applyTo(optionValue)); + }); + } + default -> { + // NOP + } + } + sb.append(": "); }); + sb.append(certRequest).append('}'); return sb.toString(); } @@ -527,35 +782,127 @@ public final class MacHelper { 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; - } - } - } - } + public List asCmdlineArgs() { + String[] args = new String[2]; + applyTo((optionName, optionValue) -> { + args[0] = optionName; + args[1] = optionValue; + }); + return List.of(args); + } - throw new AssertionError(); + private void applyTo(BiConsumer sink) { + type.mapOptionName(certRequest.type()).ifPresent(optionName -> { + sink.accept(optionName.optionName(), optionValue()); + }); + } + + private String optionValue() { + return customOptionValue.orElseGet(() -> { + switch (type) { + case SIGN_KEY_IDENTITY, + SIGN_KEY_USER_FULL_NAME, + SIGN_KEY_IDENTITY_APP_IMAGE, + SIGN_KEY_IDENTITY_INSTALLER -> { + return certRequest.name(); + } + case SIGN_KEY_USER_SHORT_NAME -> { + return certRequest.shortName(); + } + default -> { + throw new IllegalStateException(); + } + } + }); + } + + private static final Enquoter ENQUOTER = Enquoter.identity() + .setEnquotePredicate(Enquoter.QUOTE_IF_WHITESPACES).setQuoteChar('\''); + } + + public record SignKeyOptionWithKeychain(SignKeyOption signKeyOption, ResolvedKeychain keychain) { + + public SignKeyOptionWithKeychain { + Objects.requireNonNull(signKeyOption); + Objects.requireNonNull(keychain); + } + + public SignKeyOptionWithKeychain( + SignKeyOption.Type type, + ResolvableCertificateRequest certRequest, + ResolvedKeychain keychain) { + + this(new SignKeyOption(type, certRequest), keychain); + } + + public SignKeyOptionWithKeychain( + SignKeyOption.Type type, + NamedCertificateRequestSupplier certRequestSupplier, + ResolvedKeychain keychain) { + + this(type, certRequestSupplier.certRequest(keychain), keychain); + } + + public SignKeyOptionWithKeychain( + String optionValue, + ResolvableCertificateRequest certRequest, + ResolvedKeychain keychain) { + + this(new SignKeyOption(optionValue, certRequest), keychain); + } + + public SignKeyOptionWithKeychain( + String optionValue, + NamedCertificateRequestSupplier certRequestSupplier, + ResolvedKeychain keychain) { + + this(optionValue, certRequestSupplier.certRequest(keychain), keychain); + } + + public SignKeyOptionWithKeychain( + SignKeyOption.Type type, + CertificateRequest certRequest, + ResolvedKeychain keychain) { + + this(new SignKeyOption( + type, + new ResolvableCertificateRequest( + certRequest, + keychain.asCertificateResolver(), + certRequest.toString())), + keychain); + } + + @Override + public String toString() { + return String.format("%s@%s", signKeyOption, keychain.name()); + } + + public SignKeyOption.Type type() { + return signKeyOption.type(); + } + + public ResolvableCertificateRequest certRequest() { + return signKeyOption.certRequest(); + } + + public JPackageCommand addTo(JPackageCommand cmd) { + Optional.ofNullable(cmd.getArgumentValue("--mac-signing-keychain")).ifPresentOrElse(configuredKeychain -> { + if (!configuredKeychain.equals(keychain.name())) { + throw new IllegalStateException(String.format( + "Command line [%s] already has the '--mac-signing-keychain' option, not adding another one with [%s] value", + cmd, keychain.name())); + } + }, () -> { + useKeychain(cmd, keychain); + }); + return signKeyOption.addTo(cmd); + } + + public JPackageCommand setTo(JPackageCommand cmd) { + cmd.removeArgumentWithValue("--mac-signing-keychain"); + useKeychain(cmd, keychain); + return signKeyOption.setTo(cmd); } } 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 70de6ba92af..3bbbf436300 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSign.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSign.java @@ -61,6 +61,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import javax.naming.ldap.LdapName; @@ -1132,6 +1133,18 @@ public final class MacSign { return certMap; } + public Function asCertificateResolver() { + return certRequest -> { + if (!spec.certificateRequests().contains(certRequest)) { + throw new IllegalArgumentException(String.format( + "Certificate request %s not found in [%s] keychain", + certRequest, name())); + } else { + return Objects.requireNonNull(mapCertificateRequests().get(certRequest)); + } + }; + } + private final KeychainWithCertsSpec spec; private volatile Map certMap; } 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 9c469c9362e..ddf899d603c 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSignVerify.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSignVerify.java @@ -33,6 +33,7 @@ import java.util.Objects; import java.util.Optional; import java.util.regex.Pattern; import jdk.jpackage.internal.util.PListReader; +import jdk.jpackage.test.MacHelper.ResolvableCertificateRequest; import jdk.jpackage.test.MacSign.CertificateHash; import jdk.jpackage.test.MacSign.CertificateRequest; @@ -41,12 +42,10 @@ import jdk.jpackage.test.MacSign.CertificateRequest; */ public final class MacSignVerify { - public static void verifyAppImageSigned( - JPackageCommand cmd, CertificateRequest certRequest, MacSign.ResolvedKeychain keychain) { + public static void verifyAppImageSigned(JPackageCommand cmd, ResolvableCertificateRequest certRequest) { - cmd.verifyIsOfType(PackageType.MAC); + cmd.verifyIsOfType(PackageType.MAC_DMG, PackageType.MAC_PKG, PackageType.IMAGE); Objects.requireNonNull(certRequest); - Objects.requireNonNull(keychain); final Path bundleRoot; if (cmd.isImagePackageType()) { @@ -70,13 +69,12 @@ public final class MacSignVerify { String.format("Check [%s] has sign origin as expected", bundleRoot)); } - public static void verifyPkgSigned(JPackageCommand cmd, CertificateRequest certRequest, MacSign.ResolvedKeychain keychain) { + public static void verifyPkgSigned(JPackageCommand cmd, ResolvableCertificateRequest certRequest) { cmd.verifyIsOfType(PackageType.MAC_PKG); - assertPkgSigned(cmd.outputBundle(), certRequest, - Objects.requireNonNull(keychain.mapCertificateRequests().get(certRequest))); + assertPkgSigned(cmd.outputBundle(), certRequest); } - public static void assertSigned(Path path, CertificateRequest certRequest) { + public static void assertSigned(Path path, ResolvableCertificateRequest certRequest) { assertSigned(path); TKit.assertEquals(certRequest.name(), findCodesignSignOrigin(path).orElse(null), String.format("Check [%s] signed with certificate", path)); @@ -108,6 +106,10 @@ public final class MacSignVerify { String.format("Check [%s] unsigned", path)); } + public static void assertPkgSigned(Path path, ResolvableCertificateRequest certRequest) { + assertPkgSigned(path, certRequest.certRequest(), certRequest.cert()); + } + public static void assertPkgSigned(Path path, CertificateRequest certRequest, X509Certificate cert) { final var expectedCertChain = List.of(new SignIdentity(certRequest.name(), CertificateHash.of(cert, SHA256))); final var actualCertChain = getPkgCertificateChain(path); diff --git a/test/jdk/tools/jpackage/macosx/EntitlementsTest.java b/test/jdk/tools/jpackage/macosx/EntitlementsTest.java index 6e03c858db3..052e301a9ae 100644 --- a/test/jdk/tools/jpackage/macosx/EntitlementsTest.java +++ b/test/jdk/tools/jpackage/macosx/EntitlementsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -56,10 +56,9 @@ import jdk.jpackage.test.TKit; * @test * @summary jpackage with --type app-image "--mac-entitlements" parameter * @library /test/jdk/tools/jpackage/helpers - * @library base - * @build SigningBase * @build jdk.jpackage.test.* - * @build EntitlementsTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror EntitlementsTest.java * @requires (jpackage.test.MacSignTests == "run") * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main * --jpt-run=EntitlementsTest @@ -142,7 +141,8 @@ public class EntitlementsTest { cmd.mutate(MacHelper.useKeychain(keychain)).mutate(new SignKeyOption( SignKeyOption.Type.SIGN_KEY_IDENTITY, - SigningBase.StandardCertificateRequest.CODESIGN.spec() + SigningBase.StandardCertificateRequest.CODESIGN, + keychain )::addTo); cmd.mutate(new AdditionalLauncher("x")::applyTo); diff --git a/test/jdk/tools/jpackage/macosx/MacSignTest.java b/test/jdk/tools/jpackage/macosx/MacSignTest.java index af7cf448bdc..a824fdb0925 100644 --- a/test/jdk/tools/jpackage/macosx/MacSignTest.java +++ b/test/jdk/tools/jpackage/macosx/MacSignTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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,12 +21,16 @@ * questions. */ +import static jdk.jpackage.test.MacHelper.SignKeyOption.Type.SIGN_KEY_IDENTITY; +import static jdk.jpackage.test.MacHelper.SignKeyOption.Type.SIGN_KEY_USER_FULL_NAME; +import static jdk.jpackage.test.MacHelper.SignKeyOption.Type.SIGN_KEY_USER_SHORT_NAME; +import static jdk.jpackage.test.MacHelper.SignKeyOption.Type.SIGN_KEY_IDENTITY_APP_IMAGE; + import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; @@ -37,8 +41,11 @@ import jdk.jpackage.test.CannedFormattedString; import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.JPackageStringBundle; import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacHelper.NamedCertificateRequestSupplier; +import jdk.jpackage.test.MacHelper.ResolvableCertificateRequest; +import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacHelper.SignKeyOptionWithKeychain; import jdk.jpackage.test.MacSign; -import jdk.jpackage.test.MacSign.CertificateRequest; import jdk.jpackage.test.MacSign.CertificateType; import jdk.jpackage.test.MacSignVerify; import jdk.jpackage.test.PackageType; @@ -48,10 +55,9 @@ import jdk.jpackage.test.TKit; * @test * @summary jpackage with --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base - * @build SigningBase * @build jdk.jpackage.test.* - * @build MacSignTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror MacSignTest.java * @requires (jpackage.test.MacSignTests == "run") * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main * --jpt-run=MacSignTest @@ -79,22 +85,28 @@ public class MacSignTest { } MacSign.withKeychain(keychain -> { + + var signingKeyOption = new SignKeyOptionWithKeychain( + SIGN_KEY_IDENTITY, + SigningBase.StandardCertificateRequest.CODESIGN, + 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. // To make jpackage fail, specify bad additional content. - final var cmd = JPackageCommand.helloAppImage() + JPackageCommand.helloAppImage() .ignoreDefaultVerbose(true) .validateOutput(expectedStrings.toArray(CannedFormattedString[]::new)) .addArguments("--app-content", appContent) - .addArguments("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN.spec().name()); + .mutate(signingKeyOption::addTo) + .mutate(cmd -> { + if (MacHelper.isXcodeDevToolsInstalled()) { + // Check there is no warning about missing xcode command line developer tools. + cmd.validateOutput(TKit.assertTextStream(xcodeWarning.getValue()).negate()); + } + }).execute(1); - if (MacHelper.isXcodeDevToolsInstalled()) { - // Check there is no warning about missing xcode command line developer tools. - cmd.validateOutput(TKit.assertTextStream(xcodeWarning.getValue()).negate()); - } - - MacHelper.useKeychain(cmd, keychain).execute(1); }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain()); } @@ -116,13 +128,19 @@ public class MacSignTest { expectedStrings.add(JPackageStringBundle.MAIN.cannedFormattedString("error.tool.failed.with.output", "codesign")); MacSign.withKeychain(keychain -> { - final var cmd = new JPackageCommand().setPackageType(PackageType.IMAGE) + + var signingKeyOption = new SignKeyOptionWithKeychain( + SIGN_KEY_IDENTITY, + SigningBase.StandardCertificateRequest.CODESIGN, + keychain); + + 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()); + .mutate(signingKeyOption::addTo) + .execute(1); - MacHelper.useKeychain(cmd, keychain).execute(1); }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain()); } @@ -189,71 +207,78 @@ public class MacSignTest { @Test @ParameterSupplier @ParameterSupplier("testSelectSigningIdentity_JDK_8371094") - public static void testSelectSigningIdentity(String signingKeyUserName, CertificateRequest certRequest) { + public static void testSelectSigningIdentity(SignKeyOptionWithKeychain signKeyOption) { MacSign.withKeychain(keychain -> { - final var cmd = MacHelper.useKeychain(JPackageCommand.helloAppImage(), keychain) - .setFakeRuntime() - .addArguments("--mac-signing-key-user-name", signingKeyUserName); + final var cmd = JPackageCommand.helloAppImage().setFakeRuntime().mutate(signKeyOption::addTo); - cmd.executeAndAssertHelloAppImageCreated(); + cmd.executeAndAssertImageCreated(); - MacSignVerify.assertSigned(cmd.outputBundle(), certRequest); + MacSignVerify.verifyAppImageSigned(cmd, signKeyOption.certRequest()); }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain()); } public static Collection testSelectSigningIdentity() { + var keychain = SigningBase.StandardKeychain.MAIN.keychain(); return Stream.of( SigningBase.StandardCertificateRequest.CODESIGN, SigningBase.StandardCertificateRequest.CODESIGN_UNICODE - ).map(SigningBase.StandardCertificateRequest::spec).mapMulti((certRequest, acc) -> { - acc.accept(new Object[] {certRequest.shortName(), certRequest}); - acc.accept(new Object[] {certRequest.name(), certRequest}); + ).map(certRequest -> { + return Stream.of( + SIGN_KEY_USER_FULL_NAME, + SIGN_KEY_USER_SHORT_NAME + ).map(type -> { + return new SignKeyOptionWithKeychain(type, certRequest, keychain); + }); + }).flatMap(x -> x).map(v -> { + return new Object[] {v}; }).toList(); } public static Collection testSelectSigningIdentity_JDK_8371094() { return List.of(new Object[] { - "ACME Technologies Limited", SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD.spec() + new SignKeyOptionWithKeychain( + "ACME Technologies Limited", + SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD, + SigningBase.StandardKeychain.MAIN.keychain()) }); } enum SignOption { - EXPIRED_SIGNING_KEY_USER_NAME("--mac-signing-key-user-name", SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED.spec(), true, false), - EXPIRED_SIGNING_KEY_USER_NAME_PKG("--mac-signing-key-user-name", SigningBase.StandardCertificateRequest.PKG_EXPIRED.spec(), true, false), - EXPIRED_SIGN_IDENTITY("--mac-signing-key-user-name", SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED.spec(), false, false), - EXPIRED_CODESIGN_SIGN_IDENTITY("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED.spec(), false, true), - EXPIRED_PKG_SIGN_IDENTITY("--mac-installer-sign-identity", SigningBase.StandardCertificateRequest.PKG_EXPIRED.spec(), false, true), - GOOD_SIGNING_KEY_USER_NAME("--mac-signing-key-user-name", SigningBase.StandardCertificateRequest.CODESIGN.spec(), true, false), - GOOD_SIGNING_KEY_USER_NAME_PKG("--mac-signing-key-user-name", SigningBase.StandardCertificateRequest.PKG.spec(), true, false), - GOOD_CODESIGN_SIGN_IDENTITY("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN.spec(), false, true), - GOOD_PKG_SIGN_IDENTITY("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.PKG.spec(), false, true); + EXPIRED_SIGNING_KEY_USER_NAME(SIGN_KEY_USER_SHORT_NAME, SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED), + EXPIRED_SIGNING_KEY_USER_NAME_PKG(SIGN_KEY_USER_SHORT_NAME, SigningBase.StandardCertificateRequest.PKG_EXPIRED), + EXPIRED_SIGN_IDENTITY(SIGN_KEY_USER_FULL_NAME, SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED), + EXPIRED_CODESIGN_SIGN_IDENTITY(SIGN_KEY_IDENTITY, SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED), + EXPIRED_PKG_SIGN_IDENTITY(SIGN_KEY_IDENTITY, SigningBase.StandardCertificateRequest.PKG_EXPIRED), + GOOD_SIGNING_KEY_USER_NAME(SIGN_KEY_USER_SHORT_NAME, SigningBase.StandardCertificateRequest.CODESIGN), + GOOD_SIGNING_KEY_USER_NAME_PKG(SIGN_KEY_USER_SHORT_NAME, SigningBase.StandardCertificateRequest.PKG), + GOOD_CODESIGN_SIGN_IDENTITY(SIGN_KEY_IDENTITY, SigningBase.StandardCertificateRequest.CODESIGN), + GOOD_PKG_SIGN_IDENTITY(SIGN_KEY_IDENTITY_APP_IMAGE, SigningBase.StandardCertificateRequest.PKG); - SignOption(String option, MacSign.CertificateRequest cert, boolean shortName, boolean passThrough) { - this.option = Objects.requireNonNull(option); - this.cert = Objects.requireNonNull(cert); - this.shortName = shortName; - this.passThrough = passThrough; + SignOption(SignKeyOption.Type optionType, NamedCertificateRequestSupplier certRequestSupplier) { + this.option = new SignKeyOption(optionType, new ResolvableCertificateRequest(certRequestSupplier.certRequest(), _ -> { + throw new UnsupportedOperationException(); + }, certRequestSupplier.name())); } boolean passThrough() { - return passThrough; + return option.type().mapOptionName(option.certRequest().type()).orElseThrow().passThrough(); } boolean expired() { - return cert.expired(); + return option.certRequest().expired(); } String identityName() { - return cert.name(); + return option.certRequest().name(); } CertificateType identityType() { - return cert.type(); + return option.certRequest().type(); } List args() { - return List.of(option, shortName ? cert.shortName() : cert.name()); + return option.asCmdlineArgs(); } static JPackageCommand configureOutputValidation(JPackageCommand cmd, List options, @@ -274,9 +299,6 @@ public class MacSignTest { return cmd; } - private final String option; - private final MacSign.CertificateRequest cert; - private final boolean shortName; - private final boolean passThrough; + private final SignKeyOption option; } } diff --git a/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java b/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java index 37ba8a6c299..0f299cb5a24 100644 --- a/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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,90 +21,76 @@ * questions. */ -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; - -import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import jdk.jpackage.test.AdditionalLauncher; -import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacHelper.SignKeyOptionWithKeychain; import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSignVerify; /** - * Tests generation of app image with --mac-sign and related arguments. Test will - * generate app image and verify signature of main launcher and app bundle itself. - * This test requires that machine is configured with test certificate for - * "Developer ID Application: jpackage.openjdk.java.net" or alternately - * "Developer ID Application: " + name specified by system property: - * "jpackage.mac.signing.key.user.name" - * in the jpackagerTest keychain (or alternately the keychain specified with - * the system property "jpackage.mac.signing.keychain". - * If this certificate is self-signed, it must have be set to - * always allowed access to this keychain" for user which runs test. - * (If cert is real (not self signed), the do not set trust to allow.) + * Tests signing of an app image. + * + *

+ * Prerequisites: Keychains with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN} and + * {@link SigningBase.StandardKeychain#SINGLE}. */ /* * @test * @summary jpackage with --type app-image --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningAppImageTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningAppImageTest.java * @requires (jpackage.test.MacSignTests == "run") - * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main * --jpt-run=SigningAppImageTest * --jpt-before-run=SigningBase.verifySignTestEnvReady */ public class SigningAppImageTest { @Test - // ({"sign or not", "signing-key or sign-identity", "certificate index"}) - // Sign, signing-key and ASCII certificate - @Parameter({"true", "true", "ASCII_INDEX"}) - // Sign, signing-key and UNICODE certificate - @Parameter({"true", "true", "UNICODE_INDEX"}) - // Sign, signing-indentity and UNICODE certificate - @Parameter({"true", "false", "UNICODE_INDEX"}) - // 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()); - } + @ParameterSupplier + public static void test(SignKeyOptionWithKeychain sign) { - private void test(MacSign.ResolvedKeychain keychain, boolean doSign, boolean signingKey, SigningBase.CertIndex certEnum) throws Exception { - final var certIndex = certEnum.value(); + var cmd = JPackageCommand.helloAppImage(); - JPackageCommand cmd = JPackageCommand.helloAppImage(); - if (doSign) { - cmd.addArguments("--mac-sign", - "--mac-signing-keychain", - keychain.name()); - if (signingKey) { - cmd.addArguments("--mac-signing-key-user-name", - SigningBase.getDevName(certIndex)); - } else { - cmd.addArguments("--mac-app-image-sign-identity", - SigningBase.getAppCert(certIndex)); - } - } - AdditionalLauncher testAL = new AdditionalLauncher("testAL"); + var testAL = new AdditionalLauncher("testAL"); testAL.applyTo(cmd); cmd.executeAndAssertHelloAppImageCreated(); - Path launcherPath = cmd.appLauncherPath(); - SigningBase.verifyCodesign(launcherPath, doSign, certIndex); + MacSign.withKeychain(keychain -> { + sign.addTo(cmd); + cmd.executeAndAssertHelloAppImageCreated(); + MacSignVerify.verifyAppImageSigned(cmd, sign.certRequest()); + }, sign.keychain()); + } - Path testALPath = launcherPath.getParent().resolve("testAL"); - SigningBase.verifyCodesign(testALPath, doSign, certIndex); + public static Collection test() { - Path appImage = cmd.outputBundle(); - SigningBase.verifyCodesign(appImage, doSign, certIndex); - if (doSign) { - SigningBase.verifySpctl(appImage, "exec", certIndex); + List data = new ArrayList<>(); + + for (var certRequest : List.of( + SigningBase.StandardCertificateRequest.CODESIGN, + SigningBase.StandardCertificateRequest.CODESIGN_UNICODE + )) { + for (var signIdentityType : SignKeyOption.Type.defaultValues()) { + data.add(new SignKeyOptionWithKeychain( + signIdentityType, + certRequest, + SigningBase.StandardKeychain.MAIN.keychain())); + } } + + return data.stream().map(v -> { + return new Object[] {v}; + }).toList(); } } diff --git a/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java b/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java index 906734e6a9c..2c7ae205e69 100644 --- a/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, 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,38 +21,40 @@ * questions. */ -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; - -import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import jdk.jpackage.test.AdditionalLauncher; -import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacHelper.SignKeyOptionWithKeychain; import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSignVerify; import jdk.jpackage.test.PackageType; import jdk.jpackage.test.TKit; /** - * Tests generation of app image and then signs generated app image with --mac-sign - * and related arguments. Test will generate app image and verify signature of main - * launcher and app bundle itself. This test requires that machine is configured with - * test certificate for "Developer ID Application: jpackage.openjdk.java.net" or - * alternately "Developer ID Application: " + name specified by system property: - * "jpackage.mac.signing.key.user.name" in the jpackagerTest keychain - * (or alternately the keychain specified with the system property - * "jpackage.mac.signing.keychain". If this certificate is self-signed, it must - * have be set to always allowed access to this keychain" for user which runs test. - * (If cert is real (not self signed), the do not set trust to allow.) + * Tests signing of a signed/unsigned predefined app image. + * + *

+ * Prerequisites: Keychains with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN} and + * {@link SigningBase.StandardKeychain#SINGLE}. */ /* * @test * @summary jpackage with --type app-image --app-image "appImage" --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningAppImageTwoStepsTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningAppImageTwoStepsTest.java * @requires (jpackage.test.MacSignTests == "run") * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main * --jpt-run=SigningAppImageTwoStepsTest @@ -61,67 +63,123 @@ import jdk.jpackage.test.TKit; public class SigningAppImageTwoStepsTest { @Test - // ({"sign or not", "signing-key or sign-identity"}) - // Sign and signing-key - @Parameter({"true", "true"}) - // Sign and sign-identity - @Parameter({"true", "false"}) - // 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()); + @ParameterSupplier + public static void test(TestSpec spec) { + spec.test(); } - private static void test(MacSign.ResolvedKeychain keychain, boolean signAppImage, boolean signingKey) throws Exception { + public record TestSpec(Optional signAppImage, SignKeyOptionWithKeychain sign) { - Path appimageOutput = TKit.createTempDirectory("appimage"); + public TestSpec { + Objects.requireNonNull(signAppImage); + Objects.requireNonNull(sign); + } - // 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-keychain", - keychain.name()); - 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)); + @Override + public String toString() { + return Stream.of( + String.format("app-image=%s", signAppImage.map(Objects::toString).orElse("unsigned")), + sign.toString() + ).collect(Collectors.joining("; ")); + } + + static Builder build() { + return new Builder(); + } + + static class Builder { + + TestSpec create() { + return new TestSpec(Optional.ofNullable(signAppImage), sign); + } + + Builder certRequest(SigningBase.StandardCertificateRequest v) { + certRequest = Objects.requireNonNull(v); + return this; + } + + Builder signIdentityType(SignKeyOption.Type v) { + signIdentityType = Objects.requireNonNull(v); + return this; + } + + Builder sign() { + sign = createSignKeyOption(); + return this; + } + + Builder signAppImage() { + signAppImage = createSignKeyOption(); + return this; + } + + private SignKeyOptionWithKeychain createSignKeyOption() { + return new SignKeyOptionWithKeychain( + signIdentityType, + certRequest, + SigningBase.StandardKeychain.MAIN.keychain()); + } + + private SigningBase.StandardCertificateRequest certRequest = SigningBase.StandardCertificateRequest.CODESIGN; + private SignKeyOption.Type signIdentityType = SignKeyOption.Type.SIGN_KEY_IDENTITY; + + private SignKeyOptionWithKeychain signAppImage; + private SignKeyOptionWithKeychain sign; + } + + void test() { + var appImageCmd = JPackageCommand.helloAppImage() + .setFakeRuntime() + .setArgumentValue("--dest", TKit.createTempDirectory("appimage")); + + // Add an additional launcher + AdditionalLauncher testAL = new AdditionalLauncher("testAL"); + testAL.applyTo(appImageCmd); + + signAppImage.ifPresentOrElse(signOption -> { + MacSign.withKeychain(keychain -> { + signOption.addTo(appImageCmd); + appImageCmd.execute(); + MacSignVerify.verifyAppImageSigned(appImageCmd, signOption.certRequest()); + }, signOption.keychain()); + }, appImageCmd::execute); + + var cmd = new JPackageCommand() + .setPackageType(PackageType.IMAGE) + .addArguments("--app-image", appImageCmd.outputBundle()) + .mutate(sign::addTo); + + cmd.executeAndAssertHelloAppImageCreated(); + MacSignVerify.verifyAppImageSigned(cmd, sign.certRequest()); + } + } + + public static Collection test() { + + List data = new ArrayList<>(); + + for (var appImageSign : withAndWithout(SignKeyOption.Type.SIGN_KEY_IDENTITY)) { + var builder = TestSpec.build(); + appImageSign.ifPresent(signIdentityType -> { + // Sign the input app image bundle with the key not used in the jpackage command line being tested. + // This way we can test if jpackage keeps or replaces the signature of the input app image bundle. + builder.signIdentityType(signIdentityType) + .certRequest(SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD) + .signAppImage(); + }); + for (var signIdentityType : SignKeyOption.Type.defaultValues()) { + builder.signIdentityType(signIdentityType) + .certRequest(SigningBase.StandardCertificateRequest.CODESIGN); + data.add(builder.sign().create()); } } - // Add addtional launcher - AdditionalLauncher testAL = new AdditionalLauncher("testAL"); - testAL.applyTo(appImageCmd); + return data.stream().map(v -> { + return new Object[] {v}; + }).toList(); + } - // Generate app image - appImageCmd.executeAndAssertHelloAppImageCreated(); - - // Double check if it is signed or unsigned based on signAppImage - SigningBase.verifyAppImageSignature(appImageCmd, signAppImage, "testAL"); - - // Sign app image - JPackageCommand cmd = new JPackageCommand(); - cmd.setPackageType(PackageType.IMAGE) - .addArguments("--app-image", appImageCmd.outputBundle().toAbsolutePath()) - .addArguments("--mac-sign") - .addArguments("--mac-signing-keychain", keychain.name()); - if (signingKey) { - cmd.addArguments("--mac-signing-key-user-name", - SigningBase.getDevName(SigningBase.DEFAULT_INDEX)); - } else { - cmd.addArguments("--mac-app-image-sign-identity", - SigningBase.getAppCert(SigningBase.DEFAULT_INDEX)); - } - cmd.executeAndAssertImageCreated(); - - // Should be signed app image - SigningBase.verifyAppImageSignature(appImageCmd, true, "testAL"); + private static List> withAndWithout(T value) { + return List.of(Optional.empty(), Optional.of(value)); } } diff --git a/test/jdk/tools/jpackage/macosx/SigningBase.java b/test/jdk/tools/jpackage/macosx/SigningBase.java new file mode 100644 index 00000000000..5f4367e6096 --- /dev/null +++ b/test/jdk/tools/jpackage/macosx/SigningBase.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2019, 2026, 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.security.cert.X509Certificate; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; +import jdk.jpackage.test.MacHelper.NamedCertificateRequestSupplier; +import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSign.CertificateRequest; +import jdk.jpackage.test.MacSign.CertificateType; +import jdk.jpackage.test.MacSign.KeychainWithCertsSpec; +import jdk.jpackage.test.MacSign.ResolvedKeychain; +import jdk.jpackage.test.TKit; + + +/* + * @test + * @summary Setup the environment for jpackage macos signing tests. + * Creates required keychains and signing identities. + * Does NOT run any jpackag tests. + * @library /test/jdk/tools/jpackage/helpers + * @build jdk.jpackage.test.* + * @compile -Xlint:all -Werror SigningBase.java + * @requires (jpackage.test.MacSignTests == "setup") + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main + * --jpt-run=SigningBase.setUp + */ + +/* + * @test + * @summary Tear down the environment for jpackage macos signing tests. + * Deletes required keychains and signing identities. + * Does NOT run any jpackag tests. + * @library /test/jdk/tools/jpackage/helpers + * @build jdk.jpackage.test.* + * @compile -Xlint:all -Werror SigningBase.java + * @requires (jpackage.test.MacSignTests == "teardown") + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main + * --jpt-run=SigningBase.tearDown + */ + +public class SigningBase { + + public enum StandardCertificateRequest implements NamedCertificateRequestSupplier { + CODESIGN(cert().userName(NAME_ASCII)), + CODESIGN_COPY(cert().days(100).userName(NAME_ASCII)), + CODESIGN_ACME_TECH_LTD(cert().days(100).userName("ACME Technologies Limited (ABC12345)")), + PKG(cert().type(CertificateType.INSTALLER).userName(NAME_ASCII)), + PKG_COPY(cert().type(CertificateType.INSTALLER).days(100).userName(NAME_ASCII)), + CODESIGN_UNICODE(cert().userName(NAME_UNICODE)), + PKG_UNICODE(cert().type(CertificateType.INSTALLER).userName(NAME_UNICODE)), + CODESIGN_EXPIRED(cert().expired().userName("expired jpackage test")), + PKG_EXPIRED(cert().expired().type(CertificateType.INSTALLER).userName("expired jpackage test")); + + StandardCertificateRequest(CertificateRequest.Builder specBuilder) { + this.spec = specBuilder.create(); + } + + @Override + public CertificateRequest certRequest() { + return spec; + } + + private static CertificateRequest.Builder cert() { + return new CertificateRequest.Builder(); + } + + private final CertificateRequest spec; + } + + /** + * Standard keychains used in signing tests. + */ + public enum StandardKeychain { + /** + * The primary keychain with good certificates. + */ + MAIN("jpackagerTest.keychain", + StandardCertificateRequest.CODESIGN, + StandardCertificateRequest.PKG, + StandardCertificateRequest.CODESIGN_UNICODE, + StandardCertificateRequest.PKG_UNICODE, + StandardCertificateRequest.CODESIGN_ACME_TECH_LTD), + /** + * 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, + StandardCertificateRequest.CODESIGN_COPY, + StandardCertificateRequest.PKG_COPY), + ; + + StandardKeychain(String keychainName, StandardCertificateRequest... certs) { + this(keychainName, + certs[0].certRequest(), + Stream.of(certs).skip(1).map(StandardCertificateRequest::certRequest).toArray(CertificateRequest[]::new)); + } + + StandardKeychain(String keychainName, CertificateRequest cert, CertificateRequest... otherCerts) { + final var builder = keychain(keychainName).addCert(cert); + List.of(otherCerts).forEach(builder::addCert); + this.keychain = new ResolvedKeychain(builder.create()); + } + + public ResolvedKeychain keychain() { + return keychain; + } + + public X509Certificate mapCertificateRequest(CertificateRequest certRequest) { + return Objects.requireNonNull(keychain.mapCertificateRequests().get(certRequest)); + } + + public boolean contains(StandardCertificateRequest certRequest) { + return keychain.spec().certificateRequests().contains(certRequest.spec); + } + + private static KeychainWithCertsSpec.Builder keychain(String name) { + return new KeychainWithCertsSpec.Builder().name(name); + } + + private static List signingEnv() { + return Stream.of(values()).map(StandardKeychain::keychain).map(ResolvedKeychain::spec).toList(); + } + + private final ResolvedKeychain keychain; + } + + public static void setUp() { + MacSign.setUp(StandardKeychain.signingEnv()); + } + + public static void tearDown() { + MacSign.tearDown(StandardKeychain.signingEnv()); + } + + public static void verifySignTestEnvReady() { + if (!Inner.SIGN_ENV_READY) { + TKit.throwSkippedException(new IllegalStateException("Misconfigured signing test environment")); + } + } + + private final class Inner { + private static final boolean SIGN_ENV_READY = MacSign.isDeployed(StandardKeychain.signingEnv()); + } + + private static final String NAME_ASCII = "jpackage.openjdk.java.net"; + private static final String NAME_UNICODE = "jpackage.openjdk.java.net (ö)"; +} diff --git a/test/jdk/tools/jpackage/macosx/SigningPackageTest.java b/test/jdk/tools/jpackage/macosx/SigningPackageTest.java index b1e9155dacb..8a93ce5f749 100644 --- a/test/jdk/tools/jpackage/macosx/SigningPackageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningPackageTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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,180 +21,247 @@ * questions. */ -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; - -import java.nio.file.Path; -import jdk.jpackage.test.Annotations.Parameter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.SequencedSet; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import jdk.jpackage.internal.util.function.ExceptionBox; +import jdk.jpackage.test.Annotations.ParameterSupplier; 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.MacHelper.ResolvableCertificateRequest; +import jdk.jpackage.test.MacHelper.SignKeyOption; import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSignVerify; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; /** - * 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 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 system property - * "jpackage.mac.signing.key.user.name", and - * "jpackagerTest" can be over-ridden by system property - * "jpackage.mac.signing.keychain" + * Tests bundling of .pkg and .dmg packages with various signing options. + * + *

+ * Prerequisites: Keychains with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN} and + * {@link SigningBase.StandardKeychain#SINGLE}. */ + /* * @test * @summary jpackage with --type pkg,dmg --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base * @key jpackagePlatformPackage - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningPackageTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningPackageTest.java * @requires (jpackage.test.MacSignTests == "run") * @requires (jpackage.test.SQETest != null) * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=SigningPackageTest - * --jpt-space-subst=* - * --jpt-include=SigningPackageTest.test(true,*true,*true,*ASCII_INDEX) - * --jpt-before-run=SigningBase.verifySignTestEnvReady + * --jpt-run=SigningPackageTest.test + * --jpt-space-subst=* + * --jpt-include=({--mac-signing-key-user-name:*CODESIGN},*{--mac-signing-key-user-name:*PKG},*MAC_DMG+MAC_PKG) + * --jpt-before-run=SigningBase.verifySignTestEnvReady */ /* * @test * @summary jpackage with --type pkg,dmg --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base * @key jpackagePlatformPackage - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningPackageTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningPackageTest.java * @requires (jpackage.test.MacSignTests == "run") * @requires (jpackage.test.SQETest == null) - * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=SigningPackageTest + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main + * --jpt-run=SigningPackageTest.test * --jpt-before-run=SigningBase.verifySignTestEnvReady */ public class SigningPackageTest { - private static boolean isAppImageSigned(JPackageCommand cmd) { - return cmd.hasArgument("--mac-signing-key-user-name") || - cmd.hasArgument("--mac-app-image-sign-identity"); + @Test + @ParameterSupplier + public static void test(TestSpec spec) { + MacSign.withKeychain(_ -> { + spec.test(); + }, spec.keychain()); } - private static boolean isPKGSigned(JPackageCommand cmd) { - return cmd.hasArgument("--mac-signing-key-user-name") || - cmd.hasArgument("--mac-installer-sign-identity"); + public static Collection test() { + return TestSpec.testCases(true).stream().map(v -> { + return new Object[] {v}; + }).toList(); } - private static void verifyPKG(JPackageCommand cmd) { - Path outputBundle = cmd.outputBundle(); - SigningBase.verifyPkgutil(outputBundle, isPKGSigned(cmd), getCertIndex(cmd)); - if (isPKGSigned(cmd)) { - SigningBase.verifySpctl(outputBundle, "install", getCertIndex(cmd)); - } - } + record TestSpec( + Optional appImageSignOption, + Optional packageSignOption, + Set packageTypes) { - private static void verifyDMG(JPackageCommand cmd) { - Path outputBundle = cmd.outputBundle(); - SigningBase.verifyDMG(outputBundle); - } + TestSpec { + Objects.requireNonNull(appImageSignOption); + Objects.requireNonNull(packageSignOption); + Objects.requireNonNull(packageTypes); - private static void verifyAppImageInDMG(JPackageCommand cmd) { - MacHelper.withExplodedDmg(cmd, dmgImage -> { - 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")) { - SigningBase.verifyCodesign(launcherPath, isAppImageSigned(cmd), - getCertIndex(cmd)); - SigningBase.verifyCodesign(dmgImage, isAppImageSigned(cmd), - getCertIndex(cmd)); - if (isAppImageSigned(cmd)) { - SigningBase.verifySpctl(dmgImage, "exec", getCertIndex(cmd)); + if (appImageSignOption.isEmpty() && packageSignOption.isEmpty()) { + // No signing. + throw new IllegalArgumentException(); + } + + if (packageTypes.isEmpty() || !PackageType.MAC.containsAll(packageTypes)) { + // Invalid package types. + throw new IllegalArgumentException(); + } + + if (packageSignOption.isPresent()) { + if (!packageTypes.contains(PackageType.MAC_PKG)) { + // .pkg installer should be signed, but .pkg type is missing. + throw new IllegalArgumentException(); + } + + if (appImageSignOption.isEmpty()) { + if (packageSignOption.get().type() != SignKeyOption.Type.SIGN_KEY_IDENTITY) { + // They request to sign the .pkg installer without + // the "--mac-installer-sign-identity" option, + // but didn't specify a signing option for the packaged app image. + // This is wrong because only the "--mac-installer-sign-identity" option + // allows signing a .pkg installer without signing its packaged app image. + throw new IllegalArgumentException(); + } + } else if (appImageSignOption.get().type() != packageSignOption.get().type()) { + // Signing option types should be the same. + throw new IllegalArgumentException(); } } - }); - } - private static int getCertIndex(JPackageCommand cmd) { - if (cmd.hasArgument("--mac-signing-key-user-name")) { - String devName = cmd.getArgumentValue("--mac-signing-key-user-name"); - return SigningBase.getDevNameIndex(devName); - } else { - // Signing-indentity - return SigningBase.CertIndex.UNICODE_INDEX.value(); + if (!(packageTypes instanceof SequencedSet)) { + packageTypes = new TreeSet<>(packageTypes); + } + } + + TestSpec( + Optional appImageSignOption, + Optional packageSignOption, + PackageType... packageTypes) { + this(appImageSignOption, packageSignOption, Set.of(packageTypes)); + } + + @Override + public String toString() { + return Stream.of( + signKeyOptions(), + Stream.of(packageTypes.stream().map(Object::toString).collect(Collectors.joining("+"))) + ).flatMap(x -> x).map(Object::toString).collect(Collectors.joining(", ")); + } + + Stream signKeyOptions() { + return Stream.concat(appImageSignOption.stream(), packageSignOption.stream()); + } + + Optional bundleSignIdentity(PackageType type) { + switch (type) { + case MAC_DMG -> { + return Optional.empty(); + } + case MAC_PKG -> { + return packageSignOption.map(SignKeyOption::certRequest); + } + default -> { + throw new IllegalArgumentException(); + } + } + } + + void test() { + initTest().configureHelloApp().addInstallVerifier(cmd -> { + appImageSignOption.map(SignKeyOption::certRequest).ifPresent(signIdentity -> { + MacSignVerify.verifyAppImageSigned(cmd, signIdentity); + }); + }).run(); + } + + PackageTest initTest() { + return new PackageTest().forTypes(packageTypes).mutate(test -> { + appImageSignOption.ifPresent(signOption -> { + test.addInitializer(signOption::setTo); + }); + packageSignOption.ifPresent(signOption -> { + test.forTypes(PackageType.MAC_PKG, () -> { + test.addInitializer(signOption::setTo); + }); + }); + }).addBundleVerifier(cmd -> { + bundleSignIdentity(cmd.packageType()).ifPresent(signIdentity -> { + MacSignVerify.verifyPkgSigned(cmd, signIdentity); + }); + }).addInitializer(MacHelper.useKeychain(keychain())::accept); + } + + MacSign.ResolvedKeychain keychain() { + return SigningBase.StandardKeychain.MAIN.keychain(); + } + + static List testCases(boolean withUnicode) { + + List data = new ArrayList<>(); + + List> certRequestGroups; + if (withUnicode) { + certRequestGroups = List.of( + List.of(SigningBase.StandardCertificateRequest.CODESIGN, SigningBase.StandardCertificateRequest.PKG), + List.of(SigningBase.StandardCertificateRequest.CODESIGN_UNICODE, SigningBase.StandardCertificateRequest.PKG_UNICODE) + ); + } else { + certRequestGroups = List.of( + List.of(SigningBase.StandardCertificateRequest.CODESIGN, SigningBase.StandardCertificateRequest.PKG) + ); + } + + for (var certRequests : certRequestGroups) { + for (var signIdentityType : SignKeyOption.Type.defaultValues()) { + var keychain = SigningBase.StandardKeychain.MAIN.keychain(); + var appImageSignKeyOption = new SignKeyOption(signIdentityType, certRequests.getFirst(), keychain); + var pkgSignKeyOption = new SignKeyOption(signIdentityType, certRequests.getLast(), keychain); + + switch (signIdentityType) { + case SIGN_KEY_IDENTITY -> { + // Use "--mac-installer-sign-identity" and "--mac-app-image-sign-identity" signing options. + // They allows to sign the packaged app image and the installer (.pkg) separately. + data.add(new TestSpec(Optional.of(appImageSignKeyOption), Optional.empty(), PackageType.MAC)); + data.add(new TestSpec(Optional.empty(), Optional.of(pkgSignKeyOption), PackageType.MAC_PKG)); + } + case SIGN_KEY_USER_SHORT_NAME -> { + // Use "--mac-signing-key-user-name" signing option with short user name or implicit signing option. + // It signs both the packaged app image and the installer (.pkg). + // Thus, if the installer is not signed, it can be used only with .dmg packaging. + data.add(new TestSpec(Optional.of(appImageSignKeyOption), Optional.empty(), PackageType.MAC_DMG)); + } + case SIGN_KEY_USER_FULL_NAME -> { + // Use "--mac-signing-key-user-name" signing option with the full user name. + // Like SIGN_KEY_USER_SHORT_NAME, jpackage will try to use it to sign both + // the packaged app image and the installer (.pkg). + // It will fail to sign the installer, though, because the signing identity is unsuitable. + // That is why, use it with .dmg packaging only and not with .pkg packaging. + data.add(new TestSpec(Optional.of(appImageSignKeyOption), Optional.empty(), PackageType.MAC_DMG)); + continue; + } + default -> { + // SignKeyOption.Type.defaultValues() should return + // such a sequence that makes this code location unreachable. + throw ExceptionBox.reachedUnreachable(); + } + } + data.add(new TestSpec(Optional.of(appImageSignKeyOption), Optional.of(pkgSignKeyOption), PackageType.MAC)); + } + } + + return data; } } - - @Test - // ("signing-key or sign-identity", "sign app-image", "sign pkg", "certificate index"}) - // Signing-key and ASCII certificate - @Parameter({"true", "true", "true", "ASCII_INDEX"}) - // Signing-key and UNICODE certificate - @Parameter({"true", "true", "true", "UNICODE_INDEX"}) - // Signing-indentity and UNICODE certificate - @Parameter({"false", "true", "true", "UNICODE_INDEX"}) - // Signing-indentity, but sign app-image only and UNICODE certificate - @Parameter({"false", "true", "false", "UNICODE_INDEX"}) - // 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() - .configureHelloApp() - .forTypes(PackageType.MAC) - .addInitializer(cmd -> { - cmd.addArguments("--mac-sign", - "--mac-signing-keychain", keychain.name()); - if (signingKey) { - cmd.addArguments("--mac-signing-key-user-name", - SigningBase.getDevName(certIndex)); - } else { - if (signAppImage) { - cmd.addArguments("--mac-app-image-sign-identity", - SigningBase.getAppCert(certIndex)); - } - if (signPKG) { - cmd.addArguments("--mac-installer-sign-identity", - SigningBase.getInstallerCert(certIndex)); - } - } - }) - .forTypes(PackageType.MAC_PKG) - .addBundleVerifier(SigningPackageTest::verifyPKG) - .forTypes(PackageType.MAC_DMG) - .addInitializer(cmd -> { - if (!signingKey) { - // jpackage throws expected error with - // --mac-installer-sign-identity and DMG type - cmd.removeArgumentWithValue("--mac-installer-sign-identity"); - // In case of not signing app image and DMG we need to - // remove signing completely, otherwise we will default - // to --mac-signing-key-user-name once - // --mac-installer-sign-identity is removed. - if (!signAppImage) { - cmd.removeArgumentWithValue("--mac-signing-keychain"); - cmd.removeArgument("--mac-sign"); - } - } - }) - .addBundleVerifier(SigningPackageTest::verifyDMG) - .addBundleVerifier(SigningPackageTest::verifyAppImageInDMG) - .run(); - } } diff --git a/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java b/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java index 586d8d68444..70ec0e600de 100644 --- a/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, 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,25 +21,22 @@ * questions. */ -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; - import java.nio.file.Path; 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.Collectors; import java.util.stream.Stream; import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; +import jdk.jpackage.test.CannedFormattedString; import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.JPackageStringBundle; -import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacHelper.ResolvableCertificateRequest; import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacHelper.SignKeyOptionWithKeychain; import jdk.jpackage.test.MacSign; import jdk.jpackage.test.MacSignVerify; import jdk.jpackage.test.PackageFile; @@ -52,22 +49,23 @@ import jdk.jpackage.test.TKit; * signed/unsigned .pkg or .dmg package. * *

- * Prerequisites: A keychain with self-signed certificates as specified in - * {@link SigningBase.StandardKeychain#MAIN}. + * Prerequisites: Keychains with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN} and + * {@link SigningBase.StandardKeychain#SINGLE}. */ /* * @test * @summary jpackage with --type pkg,dmg --app-image * @library /test/jdk/tools/jpackage/helpers - * @library base * @key jpackagePlatformPackage - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningPackageTwoStepTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningPackageTest.java + * @compile -Xlint:all -Werror SigningPackageTwoStepTest.java * @requires (jpackage.test.MacSignTests == "run") * @requires (jpackage.test.SQETest == null) - * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main * --jpt-run=SigningPackageTwoStepTest * --jpt-before-run=SigningBase.verifySignTestEnvReady */ @@ -75,176 +73,148 @@ public class SigningPackageTwoStepTest { @Test @ParameterSupplier - public static void test(TestSpec spec) { - MacSign.withKeychain(toConsumer(keychain -> { - spec.test(keychain); - }), SigningBase.StandardKeychain.MAIN.keychain()); + public static void test(TwoStepsTestSpec spec) { + spec.test(); } - public record TestSpec(Optional signAppImage, Map signPackage) { + @Test + public static void testBundleSignedAppImage() { - public TestSpec { - Objects.requireNonNull(signAppImage); - Objects.requireNonNull(signPackage); + var appImageCmd = JPackageCommand.helloAppImage(); - if ((signAppImage.isEmpty() && signPackage.isEmpty()) || !PackageType.MAC.containsAll(signPackage.keySet())) { - // Unexpected package types. - throw new IllegalArgumentException(); - } + var predefinedAppImageSignOption = predefinedAppImageSignOption(); - // Ensure stable result of toString() call. - if (!SortedMap.class.isInstance(signPackage)) { - signPackage = new TreeMap<>(signPackage); - } - } - - @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); - }).usePredefinedAppImage(appImageCmd).addInitializer(cmd -> { - MacHelper.useKeychain(cmd, keychain); - 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(); - } + new PackageTest().addRunOnceInitializer(() -> { + createPredefinedAppImage(appImageCmd, Optional.of(predefinedAppImageSignOption)); + }).usePredefinedAppImage(appImageCmd).addInitializer(cmd -> { + configureOutputValidator(cmd, true, false); + }).addInstallVerifier(cmd -> { + MacSignVerify.verifyAppImageSigned(cmd, predefinedAppImageSignOption.certRequest()); + }).run(); } public static Collection test() { - List data = new ArrayList<>(); + 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), + for (var signAppImage : List.of(true, false)) { + Optional appImageSignOption; + if (signAppImage) { + // Sign the predefined app image bundle with the key not used in the jpackage command line being tested. + // This way we can test if jpackage keeps or replaces the signature of + // the predefined app image bundle when backing it in the pkg or dmg installer. + appImageSignOption = Optional.of(predefinedAppImageSignOption()); + } else { + appImageSignOption = Optional.empty(); + } - // Don't sign predefined app image, sign native package. - TestSpec.build().signIdentityType(signIdentityType) - .signPackage() - .certRequest(SigningBase.StandardCertificateRequest.PKG) - .signPackage(PackageType.MAC_PKG), + for (var signPackage : SigningPackageTest.TestSpec.testCases(false)) { + data.add(new TwoStepsTestSpec(appImageSignOption, signPackage)); + } + } - // 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 data.stream().map(v -> { return new Object[] {v}; }).toList(); } + + record TwoStepsTestSpec(Optional signAppImage, SigningPackageTest.TestSpec signPackage) { + + TwoStepsTestSpec { + Objects.requireNonNull(signAppImage); + Objects.requireNonNull(signPackage); + } + + @Override + public String toString() { + return Stream.of( + String.format("app-image=%s", signAppImage.map(Objects::toString).orElse("unsigned")), + signPackage.toString() + ).collect(Collectors.joining("; ")); + } + + Optional packagedAppImageSignIdentity() { + return signAppImage.map(SignKeyOptionWithKeychain::certRequest); + } + + void test() { + + var appImageCmd = JPackageCommand.helloAppImage(); + + var test = signPackage.initTest().addRunOnceInitializer(() -> { + createPredefinedAppImage(appImageCmd, signAppImage); + }).usePredefinedAppImage(appImageCmd).addInitializer(cmd -> { + configureOutputValidator(cmd, + signAppImage.isPresent(), + (cmd.packageType() == PackageType.MAC_PKG) && signPackage.packageSignOption().isPresent()); + }).addInstallVerifier(cmd -> { + packagedAppImageSignIdentity().ifPresent(certRequest -> { + MacSignVerify.verifyAppImageSigned(cmd, certRequest); + }); + }); + + MacSign.withKeychain(_ -> { + test.run(); + }, signPackage.keychain()); + } + } + + private static SignKeyOptionWithKeychain predefinedAppImageSignOption() { + // Sign the predefined app image bundle with the key not used in the jpackage command line being tested. + // This way we can test if jpackage keeps or replaces the signature of the input app image bundle. + return new SignKeyOptionWithKeychain( + SignKeyOption.Type.SIGN_KEY_USER_SHORT_NAME, + SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD, + SigningBase.StandardKeychain.MAIN.keychain()); + } + + private static void createPredefinedAppImage(JPackageCommand appImageCmd, Optional signAppImage) { + Objects.requireNonNull(appImageCmd); + Objects.requireNonNull(signAppImage); + + appImageCmd.setFakeRuntime().setArgumentValue("--dest", TKit.createTempDirectory("appimage")); + + signAppImage.ifPresentOrElse(signOption -> { + signOption.setTo(appImageCmd); + + MacSign.withKeychain(_ -> { + appImageCmd.execute(0); + }, signOption.keychain()); + + // Verify that the predefined app image is signed. + MacSignVerify.verifyAppImageSigned(appImageCmd, signOption.certRequest()); + }, () -> { + appImageCmd.execute(0); + }); + } + + private static void configureOutputValidator(JPackageCommand cmd, boolean signAppImage, boolean signPackage) { + var signedPredefinedAppImageWarning = JPackageStringBundle.MAIN.cannedFormattedString( + "warning.per.user.app.image.signed", + PackageFile.getPathInAppImage(Path.of(""))); + + var signedInstallerFromUnsignedPredefinedAppImageWarning = + JPackageStringBundle.MAIN.cannedFormattedString("warning.unsigned.app.image", "pkg"); + + // The warnings are mutually exclusive + final Optional expected; + final List unexpected = new ArrayList<>(); + + if (signAppImage) { + expected = Optional.of(signedPredefinedAppImageWarning); + } else { + unexpected.add(signedPredefinedAppImageWarning); + if (signPackage) { + expected = Optional.of(signedInstallerFromUnsignedPredefinedAppImageWarning); + } else { + expected = Optional.empty(); + unexpected.add(signedInstallerFromUnsignedPredefinedAppImageWarning); + } + } + + expected.ifPresent(cmd::validateOutput); + unexpected.forEach(str -> { + cmd.validateOutput(TKit.assertTextStream(cmd.getValue(str)).negate()); + }); + } } diff --git a/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java b/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java index ccc39f7a367..604399ebd7a 100644 --- a/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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,17 +21,31 @@ * questions. */ -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; +import static jdk.jpackage.test.JPackageCommand.RuntimeImageType.RUNTIME_TYPE_FAKE; import java.nio.file.Path; -import java.util.function.Predicate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; -import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.internal.util.MacBundle; +import jdk.jpackage.internal.util.Slot; +import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacHelper.ResolvableCertificateRequest; +import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacHelper.SignKeyOptionWithKeychain; import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSignVerify; +import jdk.jpackage.test.MacSignVerify.SpctlType; import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.TKit; /** * Tests generation of dmg and pkg with --mac-sign and related arguments. Test @@ -43,126 +57,182 @@ import jdk.jpackage.test.PackageTest; * 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 - * provided. Expected result: runtime image ad-hoc signed. - *
  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 - * provided. Expected result: runtime image is signed with original certificate. - *
  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. - * Expected result: runtime image ad-hoc signed. - *
  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: - * "jpackage.openjdk.java.net" can be over-ridden by system property - * "jpackage.mac.signing.key.user.name" + * Prerequisites: Keychains with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN} and + * {@link SigningBase.StandardKeychain#SINGLE}. */ /* * @test * @summary jpackage with --type pkg,dmg --runtime-image --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base * @key jpackagePlatformPackage - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningRuntimeImagePackageTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningPackageTest.java + * @compile -Xlint:all -Werror SigningRuntimeImagePackageTest.java * @requires (jpackage.test.MacSignTests == "run") * @requires (jpackage.test.SQETest == null) - * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main * --jpt-run=SigningRuntimeImagePackageTest * --jpt-before-run=SigningBase.verifySignTestEnvReady */ public class SigningRuntimeImagePackageTest { - 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", keychain.name(), - "--mac-signing-key-user-name", SigningBase.getDevName(certIndex)); - } - return cmd; - } - - private static Path createInputRuntimeBundle(MacSign.ResolvedKeychain keychain, int certIndex) { - return MacHelper.createRuntimeBundle(cmd -> { - addSignOptions(cmd, keychain, certIndex); - }); + @Test + @ParameterSupplier + public static void test(RuntimeTestSpec spec) { + spec.test(); } @Test - // useJDKBundle - If "true" predefined runtime image will be converted to - // JDK bundle. If "false" JDK image will be used. - // JDKBundleCert - Certificate to sign JDK bundle before calling jpackage. - // signCert - Certificate to sign bundle produced by jpackage. - // 1) unsigned JDK bundle and --mac-sign is not provided - @Parameter({"true", "INVALID_INDEX", "INVALID_INDEX"}) - // 2) unsigned JDK bundle and --mac-sign is provided - @Parameter({"true", "INVALID_INDEX", "ASCII_INDEX"}) - // 3) signed JDK bundle and --mac-sign is not provided - @Parameter({"true", "UNICODE_INDEX", "INVALID_INDEX"}) - // 4) signed JDK bundle and --mac-sign is provided - @Parameter({"true", "UNICODE_INDEX", "ASCII_INDEX"}) - // 5) JDK image and --mac-sign is not provided - @Parameter({"false", "INVALID_INDEX", "INVALID_INDEX"}) - // 6) JDK image and --mac-sign is provided - @Parameter({"false", "INVALID_INDEX", "ASCII_INDEX"}) - 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()); + public static void testBundleSignedRuntime() { + + Slot predefinedRuntime = Slot.createEmpty(); + + var signRuntime = runtimeImageSignOption(); + + new PackageTest().addRunOnceInitializer(() -> { + predefinedRuntime.set(createRuntime(Optional.of(signRuntime), RuntimeType.BUNDLE)); + }).addInitializer(cmd -> { + cmd.ignoreDefaultRuntime(true); + cmd.removeArgumentWithValue("--input"); + cmd.setArgumentValue("--runtime-image", predefinedRuntime.get()); + }).addInstallVerifier(cmd -> { + MacSignVerify.verifyAppImageSigned(cmd, signRuntime.certRequest()); + }).run(); } - private static void test(MacSign.ResolvedKeychain keychain, boolean useJDKBundle, - SigningBase.CertIndex jdkBundleCert, - SigningBase.CertIndex signCert) throws Exception { + public static Collection test() { - final Path inputRuntime[] = new Path[1]; + List data = new ArrayList<>(); - new PackageTest() - .addRunOnceInitializer(() -> { - if (useJDKBundle) { - inputRuntime[0] = createInputRuntimeBundle(keychain, jdkBundleCert.value()); - } else { - inputRuntime[0] = JPackageCommand.createInputRuntimeImage(); - } - }) - .addInitializer(cmd -> { - cmd.addArguments("--runtime-image", inputRuntime[0]); - // Remove --input parameter from jpackage command line as we don't - // create input directory in the test and jpackage fails - // if --input references non existent directory. - cmd.removeArgumentWithValue("--input"); - addSignOptions(cmd, keychain, signCert.value()); - }) - .addInstallVerifier(cmd -> { - final var certIndex = Stream.of(signCert, jdkBundleCert) - .filter(Predicate.isEqual(SigningBase.CertIndex.INVALID_INDEX).negate()) - .findFirst().orElse(SigningBase.CertIndex.INVALID_INDEX).value(); + for (var runtimeSpec : List.of( + Map.entry(RuntimeType.IMAGE, false /* unsigned */), + Map.entry(RuntimeType.BUNDLE, false /* unsigned */), + Map.entry(RuntimeType.BUNDLE, true /* signed */) + )) { + var runtimeType = runtimeSpec.getKey(); + var signRuntime = runtimeSpec.getValue(); - final var signed = certIndex != SigningBase.CertIndex.INVALID_INDEX.value(); + Optional runtimeSignOption; + if (signRuntime) { + // Sign the runtime bundle with the key not used in the jpackage command line being tested. + // This way we can test if jpackage keeps or replaces the signature of + // the predefined runtime bundle when backing it in the pkg or dmg installer. + runtimeSignOption = Optional.of(runtimeImageSignOption()); + } else { + runtimeSignOption = Optional.empty(); + } - final var unfoldedBundleDir = cmd.appRuntimeDirectory(); + for (var signPackage : SigningPackageTest.TestSpec.testCases(false)) { + data.add(new RuntimeTestSpec(runtimeSignOption, runtimeType, signPackage)); + } + } - final var libjli = unfoldedBundleDir.resolve("Contents/MacOS/libjli.dylib"); + return data.stream().map(v -> { + return new Object[] {v}; + }).toList(); + } - SigningBase.verifyCodesign(libjli, signed, certIndex); - SigningBase.verifyCodesign(unfoldedBundleDir, signed, certIndex); - if (signed) { - SigningBase.verifySpctl(unfoldedBundleDir, "exec", certIndex); - } - }) - .run(); + enum RuntimeType { + IMAGE, + BUNDLE, + ; + } + + record RuntimeTestSpec( + Optional signRuntime, + RuntimeType runtimeType, + SigningPackageTest.TestSpec signPackage) { + + RuntimeTestSpec { + Objects.requireNonNull(signRuntime); + Objects.requireNonNull(runtimeType); + Objects.requireNonNull(signPackage); + } + + @Override + public String toString() { + var runtimeToken = new StringBuilder(); + runtimeToken.append("runtime={").append(runtimeType); + signRuntime.ifPresent(v -> { + runtimeToken.append(", ").append(v); + }); + runtimeToken.append('}'); + return Stream.of(runtimeToken, signPackage).map(Objects::toString).collect(Collectors.joining("; ")); + } + + Optional packagedAppImageSignIdentity() { + if (runtimeType == RuntimeType.IMAGE) { + return signPackage.appImageSignOption().map(SignKeyOption::certRequest); + } else { + return signPackage.appImageSignOption().or(() -> { + return signRuntime.map(SignKeyOptionWithKeychain::signKeyOption); + }).map(SignKeyOption::certRequest); + } + } + + void test() { + + Slot predefinedRuntime = Slot.createEmpty(); + + var test = signPackage.initTest().addRunOnceInitializer(() -> { + predefinedRuntime.set(createRuntime(signRuntime, runtimeType)); + }).addInitializer(cmd -> { + cmd.ignoreDefaultRuntime(true); + cmd.removeArgumentWithValue("--input"); + cmd.setArgumentValue("--runtime-image", predefinedRuntime.get()); + }).addInstallVerifier(cmd -> { + packagedAppImageSignIdentity().ifPresent(certRequest -> { + MacSignVerify.verifyAppImageSigned(cmd, certRequest); + }); + }); + + MacSign.withKeychain(_ -> { + test.run(); + }, signPackage.keychain()); + } + } + + private static SignKeyOptionWithKeychain runtimeImageSignOption() { + // Sign the runtime bundle with the key not used in the jpackage command line being tested. + // This way we can test if jpackage keeps or replaces the signature of + // the predefined runtime bundle when backing it in the pkg or dmg installer. + return new SignKeyOptionWithKeychain( + SignKeyOption.Type.SIGN_KEY_USER_SHORT_NAME, + SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD, + SigningBase.StandardKeychain.MAIN.keychain()); + } + + private static Path createRuntime(Optional signRuntime, RuntimeType runtimeType) { + if (runtimeType == RuntimeType.IMAGE && signRuntime.isEmpty()) { + return JPackageCommand.createInputRuntimeImage(RUNTIME_TYPE_FAKE); + } else { + Slot runtimeBundle = Slot.createEmpty(); + + MacSign.withKeychain(keychain -> { + var runtimeBundleBuilder = MacHelper.buildRuntimeBundle(); + signRuntime.ifPresent(signingOption -> { + runtimeBundleBuilder.mutator(signingOption::setTo); + }); + runtimeBundle.set(runtimeBundleBuilder.type(RUNTIME_TYPE_FAKE).create()); + }, SigningBase.StandardKeychain.MAIN.keychain()); + + if (runtimeType == RuntimeType.IMAGE) { + return MacBundle.fromPath(runtimeBundle.get()).orElseThrow().homeDir(); + } else { + // Verify the runtime bundle is properly signed/unsigned. + signRuntime.map(SignKeyOptionWithKeychain::certRequest).ifPresentOrElse(certRequest -> { + MacSignVerify.assertSigned(runtimeBundle.get(), certRequest); + var signOrigin = MacSignVerify.findSpctlSignOrigin(SpctlType.EXEC, runtimeBundle.get()).orElse(null); + TKit.assertEquals(certRequest.name(), signOrigin, + String.format("Check [%s] has sign origin as expected", runtimeBundle.get())); + }, () -> { + MacSignVerify.assertAdhocSigned(runtimeBundle.get()); + }); + return runtimeBundle.get(); + } + } } } diff --git a/test/jdk/tools/jpackage/macosx/base/SigningBase.java b/test/jdk/tools/jpackage/macosx/base/SigningBase.java deleted file mode 100644 index b1a709c9cf0..00000000000 --- a/test/jdk/tools/jpackage/macosx/base/SigningBase.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (c) 2019, 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 - * 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 java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; -import jdk.jpackage.test.JPackageCommand; -import jdk.jpackage.test.MacSign; -import jdk.jpackage.test.MacSign.CertificateRequest; -import jdk.jpackage.test.MacSign.CertificateType; -import jdk.jpackage.test.MacSign.KeychainWithCertsSpec; -import jdk.jpackage.test.MacSign.ResolvedKeychain; -import jdk.jpackage.test.MacSignVerify; -import jdk.jpackage.test.TKit; - - -/* - * @test - * @summary Setup the environment for jpackage macos signing tests. - * Creates required keychains and signing identities. - * Does NOT run any jpackag tests. - * @library /test/jdk/tools/jpackage/helpers - * @build jdk.jpackage.test.* - * @compile -Xlint:all -Werror SigningBase.java - * @requires (jpackage.test.MacSignTests == "setup") - * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=SigningBase.setUp - */ - -/* - * @test - * @summary Tear down the environment for jpackage macos signing tests. - * Deletes required keychains and signing identities. - * Does NOT run any jpackag tests. - * @library /test/jdk/tools/jpackage/helpers - * @build jdk.jpackage.test.* - * @compile -Xlint:all -Werror SigningBase.java - * @requires (jpackage.test.MacSignTests == "teardown") - * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=SigningBase.tearDown - */ - -public class SigningBase { - - public enum StandardCertificateRequest { - CODESIGN(cert().userName(DEV_NAMES[CertIndex.ASCII_INDEX.value()])), - CODESIGN_COPY(cert().days(100).userName(DEV_NAMES[CertIndex.ASCII_INDEX.value()])), - CODESIGN_ACME_TECH_LTD(cert().days(100).userName("ACME Technologies Limited (ABC12345)")), - PKG(cert().type(CertificateType.INSTALLER).userName(DEV_NAMES[CertIndex.ASCII_INDEX.value()])), - PKG_COPY(cert().type(CertificateType.INSTALLER).days(100).userName(DEV_NAMES[CertIndex.ASCII_INDEX.value()])), - CODESIGN_UNICODE(cert().userName(DEV_NAMES[CertIndex.UNICODE_INDEX.value()])), - PKG_UNICODE(cert().type(CertificateType.INSTALLER).userName(DEV_NAMES[CertIndex.UNICODE_INDEX.value()])), - CODESIGN_EXPIRED(cert().expired().userName("expired jpackage test")), - PKG_EXPIRED(cert().expired().type(CertificateType.INSTALLER).userName("expired jpackage test")); - - StandardCertificateRequest(CertificateRequest.Builder specBuilder) { - this.spec = specBuilder.create(); - } - - public CertificateRequest spec() { - return spec; - } - - private static CertificateRequest.Builder cert() { - return new CertificateRequest.Builder(); - } - - private final CertificateRequest spec; - } - - /** - * Standard keychains used in signing tests. - */ - public enum StandardKeychain { - /** - * The primary keychain with good certificates. - */ - MAIN("jpackagerTest.keychain", - StandardCertificateRequest.CODESIGN, - StandardCertificateRequest.PKG, - StandardCertificateRequest.CODESIGN_UNICODE, - StandardCertificateRequest.PKG_UNICODE, - StandardCertificateRequest.CODESIGN_ACME_TECH_LTD), - /** - * 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, - StandardCertificateRequest.CODESIGN_COPY, - StandardCertificateRequest.PKG_COPY); - - StandardKeychain(String keychainName, StandardCertificateRequest... certs) { - this(keychainName, certs[0].spec(), Stream.of(certs).skip(1).map(StandardCertificateRequest::spec).toArray(CertificateRequest[]::new)); - } - - StandardKeychain(String keychainName, CertificateRequest cert, CertificateRequest... otherCerts) { - final var builder = keychain(keychainName).addCert(cert); - List.of(otherCerts).forEach(builder::addCert); - this.keychain = new ResolvedKeychain(builder.create()); - } - - public ResolvedKeychain keychain() { - return keychain; - } - - public X509Certificate mapCertificateRequest(CertificateRequest certRequest) { - return Objects.requireNonNull(keychain.mapCertificateRequests().get(certRequest)); - } - - private static KeychainWithCertsSpec.Builder keychain(String name) { - return new KeychainWithCertsSpec.Builder().name(name); - } - - private static List signingEnv() { - return Stream.of(values()).map(StandardKeychain::keychain).map(ResolvedKeychain::spec).toList(); - } - - private final ResolvedKeychain keychain; - } - - public static void setUp() { - MacSign.setUp(StandardKeychain.signingEnv()); - } - - public static void tearDown() { - MacSign.tearDown(StandardKeychain.signingEnv()); - } - - public static void verifySignTestEnvReady() { - if (!Inner.SIGN_ENV_READY) { - TKit.throwSkippedException(new IllegalStateException("Misconfigured signing test environment")); - } - } - - private final class Inner { - private static final boolean SIGN_ENV_READY = MacSign.isDeployed(StandardKeychain.signingEnv()); - } - - enum CertIndex { - ASCII_INDEX(0), - UNICODE_INDEX(1), - INVALID_INDEX(-1); - - CertIndex(int value) { - this.value = value; - } - - int value() { - return value; - } - - private final int value; - } - - public static int DEFAULT_INDEX = 0; - private static String [] DEV_NAMES = { - "jpackage.openjdk.java.net", - "jpackage.openjdk.java.net (ö)", - }; - - public static String getDevName(int certIndex) { - // Always use values from system properties if set - String value = System.getProperty("jpackage.mac.signing.key.user.name"); - if (value != null) { - return value; - } - - return DEV_NAMES[certIndex]; - } - - public static int getDevNameIndex(String devName) { - return Arrays.binarySearch(DEV_NAMES, devName); - } - - public static String getAppCert(int certIndex) { - return "Developer ID Application: " + getDevName(certIndex); - } - - public static String getInstallerCert(int certIndex) { - return "Developer ID Installer: " + getDevName(certIndex); - } - - public static void verifyCodesign(Path target, boolean signed, int certIndex) { - if (signed) { - final var certRequest = getCertRequest(certIndex); - MacSignVerify.assertSigned(target, certRequest); - } else { - MacSignVerify.assertAdhocSigned(target); - } - } - - // Since we no longer have unsigned app image, but we need to check - // DMG which is not adhoc or certificate signed and we cannot use verifyCodesign - // for this. verifyDMG() is introduced to check that DMG is unsigned. - // Should not be used to validated anything else. - public static void verifyDMG(Path target) { - if (!target.toString().toLowerCase().endsWith(".dmg")) { - throw new IllegalArgumentException("Unexpected target: " + target); - } - - MacSignVerify.assertUnsigned(target); - } - - public static void verifySpctl(Path target, String type, int certIndex) { - final var standardCertIndex = Stream.of(CertIndex.values()).filter(v -> { - return v.value() == certIndex; - }).findFirst().orElseThrow(); - - final var standardType = Stream.of(MacSignVerify.SpctlType.values()).filter(v -> { - return v.value().equals(type); - }).findFirst().orElseThrow(); - - final String expectedSignOrigin; - if (standardCertIndex == CertIndex.INVALID_INDEX) { - expectedSignOrigin = null; - } else if (standardType == MacSignVerify.SpctlType.EXEC) { - expectedSignOrigin = getCertRequest(certIndex).name(); - } else if (standardType == MacSignVerify.SpctlType.INSTALL) { - expectedSignOrigin = getPkgCertRequest(certIndex).name(); - } else { - throw new IllegalArgumentException(); - } - - final var signOrigin = MacSignVerify.findSpctlSignOrigin(standardType, target).orElse(null); - - TKit.assertEquals(signOrigin, expectedSignOrigin, - String.format("Check [%s] has sign origin as expected", target)); - } - - public static void verifyPkgutil(Path target, boolean signed, int certIndex) { - if (signed) { - final var certRequest = getPkgCertRequest(certIndex); - MacSignVerify.assertPkgSigned(target, certRequest, StandardKeychain.MAIN.mapCertificateRequest(certRequest)); - } else { - MacSignVerify.assertUnsigned(target); - } - } - - public static void verifyAppImageSignature(JPackageCommand appImageCmd, - boolean isSigned, String... launchers) throws Exception { - Path launcherPath = appImageCmd.appLauncherPath(); - SigningBase.verifyCodesign(launcherPath, isSigned, SigningBase.DEFAULT_INDEX); - - final List launchersList = List.of(launchers); - launchersList.forEach(launcher -> { - Path testALPath = launcherPath.getParent().resolve(launcher); - SigningBase.verifyCodesign(testALPath, isSigned, SigningBase.DEFAULT_INDEX); - }); - - Path appImage = appImageCmd.outputBundle(); - SigningBase.verifyCodesign(appImage, isSigned, SigningBase.DEFAULT_INDEX); - if (isSigned) { - SigningBase.verifySpctl(appImage, "exec", SigningBase.DEFAULT_INDEX); - } - } - - private static CertificateRequest getCertRequest(int certIndex) { - switch (CertIndex.values()[certIndex]) { - case ASCII_INDEX -> { - return StandardCertificateRequest.CODESIGN.spec(); - } - case UNICODE_INDEX -> { - return StandardCertificateRequest.CODESIGN_UNICODE.spec(); - } - default -> { - throw new IllegalArgumentException(); - } - } - } - - private static CertificateRequest getPkgCertRequest(int certIndex) { - switch (CertIndex.values()[certIndex]) { - case ASCII_INDEX -> { - return StandardCertificateRequest.PKG.spec(); - } - case UNICODE_INDEX -> { - return StandardCertificateRequest.PKG_UNICODE.spec(); - } - default -> { - throw new IllegalArgumentException(); - } - } - } -}