8375242: [macos] Improve jpackage signing coverage

Reviewed-by: almatvee
This commit is contained in:
Alexey Semenyuk 2026-01-16 23:16:43 +00:00
parent e7432d5745
commit 9b47c23b4b
13 changed files with 1413 additions and 989 deletions

View File

@ -294,34 +294,8 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
public JPackageCommand setFakeRuntime() {
verifyMutable();
ThrowingConsumer<Path, IOException> 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<JPackageCommand> {
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<Path> 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;

View File

@ -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<JPackageCommand> v) {
mutator = v;
return this;
}
public RuntimeBundleBuilder mutate(Consumer<RuntimeBundleBuilder> mutator) {
mutator.accept(this);
return this;
}
private RuntimeBundleBuilder() {
}
private JPackageCommand.RuntimeImageType type = JPackageCommand.RuntimeImageType.RUNTIME_TYPE_HELLO_APP;
private Consumer<JPackageCommand> mutator;
};
public static RuntimeBundleBuilder buildRuntimeBundle() {
return new RuntimeBundleBuilder();
}
public static Path createRuntimeBundle(Consumer<JPackageCommand> 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<Consumer<JPackageCommand>> mutator) {
private static Path createRuntimeBundle(JPackageCommand.RuntimeImageType type, Optional<Consumer<JPackageCommand>> 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<CertificateRequest, X509Certificate> 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<CertificateRequest, X509Certificate> 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<String> 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<MacSign.CertificateType, Name> 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<Name> 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<MacSign.CertificateType, Optional<Name>> 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<String, String> 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<String> 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<String, String> 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);
}
}

View File

@ -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<CertificateRequest, X509Certificate> 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<CertificateRequest, X509Certificate> certMap;
}

View File

@ -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);

View File

@ -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);

View File

@ -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<Object[]> testSelectSigningIdentity() {
var keychain = SigningBase.StandardKeychain.MAIN.keychain();
return Stream.of(
SigningBase.StandardCertificateRequest.CODESIGN,
SigningBase.StandardCertificateRequest.CODESIGN_UNICODE
).map(SigningBase.StandardCertificateRequest::spec).<Object[]>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<Object[]> testSelectSigningIdentity_JDK_8371094() {
return List.<Object[]>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<String> args() {
return List.of(option, shortName ? cert.shortName() : cert.name());
return option.asCmdlineArgs();
}
static JPackageCommand configureOutputValidation(JPackageCommand cmd, List<SignOption> 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;
}
}

View File

@ -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.
*
* <p>
* 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<Object[]> test() {
Path appImage = cmd.outputBundle();
SigningBase.verifyCodesign(appImage, doSign, certIndex);
if (doSign) {
SigningBase.verifySpctl(appImage, "exec", certIndex);
List<SignKeyOptionWithKeychain> 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();
}
}

View File

@ -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.
*
* <p>
* 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<SignKeyOptionWithKeychain> 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<Object[]> test() {
List<TestSpec> 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 <T> List<Optional<T>> withAndWithout(T value) {
return List.of(Optional.empty(), Optional.of(value));
}
}

View File

@ -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<KeychainWithCertsSpec> 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 (ö)";
}

View File

@ -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.
*
* <p>
* 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<Object[]> 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<SignKeyOption> appImageSignOption,
Optional<SignKeyOption> packageSignOption,
Set<PackageType> 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<SignKeyOption> appImageSignOption,
Optional<SignKeyOption> 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<SignKeyOption> signKeyOptions() {
return Stream.concat(appImageSignOption.stream(), packageSignOption.stream());
}
Optional<ResolvableCertificateRequest> 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<TestSpec> testCases(boolean withUnicode) {
List<TestSpec> data = new ArrayList<>();
List<List<SigningBase.StandardCertificateRequest>> 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();
}
}

View File

@ -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.
*
* <p>
* 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<SignKeyOption> signAppImage, Map<PackageType, SignKeyOption> 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<PackageType, SignKeyOption> 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<Object[]> test() {
List<TestSpec.Builder> data = new ArrayList<>();
List<TwoStepsTestSpec> 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<SignKeyOptionWithKeychain> 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<SignKeyOptionWithKeychain> 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<ResolvableCertificateRequest> 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<SignKeyOptionWithKeychain> 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<CannedFormattedString> expected;
final List<CannedFormattedString> 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());
});
}
}

View File

@ -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.
*
* <p>
* Following combinations are tested:
* <ol>
* <li>"--runtime-image" points to unsigned JDK bundle and --mac-sign is not
* provided. Expected result: runtime image ad-hoc signed.
* <li>"--runtime-image" points to unsigned JDK bundle and --mac-sign is
* provided. Expected result: Everything is signed with provided certificate.
* <li>"--runtime-image" points to signed JDK bundle and --mac-sign is not
* provided. Expected result: runtime image is signed with original certificate.
* <li>"--runtime-image" points to signed JDK bundle and --mac-sign is provided.
* Expected result: runtime image is signed with provided certificate.
* <li>"--runtime-image" points to JDK image and --mac-sign is not provided.
* Expected result: runtime image ad-hoc signed.
* <li>"--runtime-image" points to JDK image and --mac-sign is provided.
* Expected result: Everything is signed with provided certificate.
* </ol>
*
* 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<Path> 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<Object[]> test() {
final Path inputRuntime[] = new Path[1];
List<RuntimeTestSpec> 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<SignKeyOptionWithKeychain> 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<SignKeyOptionWithKeychain> 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<ResolvableCertificateRequest> 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<Path> 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<SignKeyOptionWithKeychain> signRuntime, RuntimeType runtimeType) {
if (runtimeType == RuntimeType.IMAGE && signRuntime.isEmpty()) {
return JPackageCommand.createInputRuntimeImage(RUNTIME_TYPE_FAKE);
} else {
Slot<Path> 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();
}
}
}
}

View File

@ -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<KeychainWithCertsSpec> 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<String> 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();
}
}
}
}