8370126: Improve jpackage signing testing

Reviewed-by: almatvee
This commit is contained in:
Alexey Semenyuk 2025-10-21 06:21:11 +00:00
parent 634746a0f1
commit 2aa0efd425
15 changed files with 682 additions and 272 deletions

View File

@ -26,14 +26,8 @@
package jdk.jpackage.internal;
import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE;
import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE;
import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.Map;
import java.util.Optional;
import jdk.jpackage.internal.model.ConfigException;
public abstract class MacBaseInstallerBundler extends AbstractBundler {
@ -44,26 +38,7 @@ public abstract class MacBaseInstallerBundler extends AbstractBundler {
protected void validateAppImageAndBundeler(
Map<String, ? super Object> params) throws ConfigException {
if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) {
Path applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params);
if (new MacAppImageFileExtras(PREDEFINED_APP_IMAGE_FILE.fetchFrom(params)).signed()) {
var appLayout = ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT.resolveAt(applicationImage);
if (!Files.exists(
PackageFile.getPathInAppImage(appLayout))) {
Log.info(MessageFormat.format(I18N.getString(
"warning.per.user.app.image.signed"),
PackageFile.getPathInAppImage(appLayout)));
}
} else {
if (Optional.ofNullable(
SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
// if signing bundle with app-image, warn user if app-image
// is not already signed.
Log.info(MessageFormat.format(I18N.getString(
"warning.unsigned.app.image"), getID()));
}
}
} else {
if (PREDEFINED_APP_IMAGE.fetchFrom(params) == null) {
appImageBundler.validate(params);
}
}

View File

@ -24,8 +24,10 @@
*/
package jdk.jpackage.internal;
import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT;
import static jdk.jpackage.internal.MacPackagingPipeline.LayoutUtils.packagerLayout;
import java.nio.file.Files;
import java.util.Objects;
import jdk.jpackage.internal.model.ConfigException;
import jdk.jpackage.internal.model.MacApplication;
@ -57,7 +59,21 @@ final class MacPackageBuilder {
.installedPackageLayout(pkg.installedPackageLayout());
pkg = pkgBuilder.create();
return MacPackage.create(pkg, new MacPackageMixin.Stub(pkg.predefinedAppImage().map(v -> predefinedAppImageSigned)));
var macPkg = MacPackage.create(pkg, new MacPackageMixin.Stub(pkg.predefinedAppImage().map(v -> predefinedAppImageSigned)));
validatePredefinedAppImage(macPkg);
return macPkg;
}
private static void validatePredefinedAppImage(MacPackage pkg) {
if (pkg.predefinedAppImageSigned().orElse(false)) {
pkg.predefinedAppImage().ifPresent(predefinedAppImage -> {
var thePackageFile = PackageFile.getPathInAppImage(APPLICATION_LAYOUT);
if (!Files.exists(predefinedAppImage.resolve(thePackageFile))) {
Log.info(I18N.format("warning.per.user.app.image.signed", thePackageFile));
}
});
}
}
private final PackageBuilder pkgBuilder;

View File

@ -43,7 +43,9 @@ final class MacPkgPackageBuilder {
}
MacPkgPackage create() throws ConfigException {
return MacPkgPackage.create(pkgBuilder.create(), new MacPkgPackageMixin.Stub(createSigningConfig()));
var pkg = MacPkgPackage.create(pkgBuilder.create(), new MacPkgPackageMixin.Stub(createSigningConfig()));
validatePredefinedAppImage(pkg);
return pkg;
}
private Optional<PkgSigningConfig> createSigningConfig() throws ConfigException {
@ -56,6 +58,14 @@ final class MacPkgPackageBuilder {
}
}
private static void validatePredefinedAppImage(MacPkgPackage pkg) {
if (!pkg.predefinedAppImageSigned().orElse(false) && pkg.sign()) {
pkg.predefinedAppImage().ifPresent(predefinedAppImage -> {
Log.info(I18N.format("warning.unsigned.app.image", "pkg"));
});
}
}
private final MacPackageBuilder pkgBuilder;
private SigningIdentityBuilder signingBuilder;
}

View File

@ -1131,6 +1131,11 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
MacHelper.verifyBundleStructure(cmd);
}
}),
MAC_BUNDLE_UNSIGNED_SIGNATURE(cmd -> {
if (TKit.isOSX() && !MacHelper.appImageSigned(cmd)) {
MacHelper.verifyUnsignedBundleSignature(cmd);
}
}),
;
AppLayoutAssert(Consumer<JPackageCommand> action) {

View File

@ -52,6 +52,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Pattern;
@ -66,6 +67,7 @@ import jdk.jpackage.internal.util.PathUtils;
import jdk.jpackage.internal.util.XmlUtils;
import jdk.jpackage.internal.util.function.ThrowingConsumer;
import jdk.jpackage.internal.util.function.ThrowingSupplier;
import jdk.jpackage.test.MacSign.CertificateRequest;
import jdk.jpackage.test.PackageTest.PackageHandlers;
public final class MacHelper {
@ -229,25 +231,61 @@ public final class MacHelper {
}
}
/**
* Returns {@code true} if the given jpackage command line is configured to sign
* predefined app image in place.
* <p>
* jpackage will not create a new app image or a native bundle.
*
* @param cmd the jpackage command to examine
* @return {@code true} if the given jpackage command line is configured to sign
* predefined app image in place and {@code false} otherwise.
*/
public static boolean signPredefinedAppImage(JPackageCommand cmd) {
Objects.requireNonNull(cmd);
if (!TKit.isOSX()) {
throw new UnsupportedOperationException();
}
return cmd.hasArgument("--mac-sign") && cmd.hasArgument("--app-image");
return cmd.hasArgument("--mac-sign") && cmd.hasArgument("--app-image") && cmd.isImagePackageType();
}
/**
* Returns {@code true} if the given jpackage command line is configured such
* that the app image it will produce will be signed.
* <p>
* If the jpackage command line is bundling a native package, the function
* returns {@code true} if the bundled app image will be signed.
*
* @param cmd the jpackage command to examine
* @return {@code true} if the given jpackage command line is configured such
* that the app image it will produce will be signed and {@code false}
* otherwise.
*/
public static boolean appImageSigned(JPackageCommand cmd) {
Objects.requireNonNull(cmd);
if (!TKit.isOSX()) {
throw new UnsupportedOperationException();
}
if (Optional.ofNullable(cmd.getArgumentValue("--app-image")).map(Path::of).map(AppImageFile::load).map(AppImageFile::macSigned).orElse(false)) {
var runtimeImage = Optional.ofNullable(cmd.getArgumentValue("--runtime-image")).map(Path::of);
var appImage = Optional.ofNullable(cmd.getArgumentValue("--app-image")).map(Path::of);
if (cmd.isRuntime() && Files.isDirectory(runtimeImage.orElseThrow().resolve("Contents/_CodeSignature"))) {
// If the predefined runtime is a signed bundle, bundled image should be signed too.
return true;
} else if (appImage.map(AppImageFile::load).map(AppImageFile::macSigned).orElse(false)) {
// The external app image is signed, so the app image is signed too.
return true;
}
if (!cmd.isImagePackageType() && appImage.isPresent()) {
// Building a ".pkg" or a ".dmg" bundle from the predefined app image.
// The predefined app image is unsigned, so the app image bundled
// in the output native package will be unsigned too
// (even if the ".pkg" file may be signed itself, and we never sign ".dmg" files).
return false;
}
if (!cmd.hasArgument("--mac-sign")) {
return false;
}
@ -332,6 +370,100 @@ public final class MacHelper {
}).run();
}
public static JPackageCommand useKeychain(JPackageCommand cmd, MacSign.ResolvedKeychain keychain) {
return useKeychain(cmd, keychain.spec().keychain());
}
public static JPackageCommand useKeychain(JPackageCommand cmd, MacSign.Keychain keychain) {
return sign(cmd).addArguments("--mac-signing-keychain", keychain.name());
}
public static JPackageCommand sign(JPackageCommand cmd) {
if (!cmd.hasArgument("--mac-sign")) {
cmd.addArgument("--mac-sign");
}
return cmd;
}
public record SignKeyOption(Type type, CertificateRequest certRequest) {
public SignKeyOption {
Objects.requireNonNull(type);
Objects.requireNonNull(certRequest);
}
public enum Type {
SIGN_KEY_USER_NAME,
SIGN_KEY_IDENTITY,
;
}
@Override
public String toString() {
var sb = new StringBuffer();
applyTo((optionName, _) -> {
sb.append(String.format("{%s: %s}", optionName, certRequest));
});
return sb.toString();
}
public JPackageCommand addTo(JPackageCommand cmd) {
applyTo(cmd::addArguments);
return sign(cmd);
}
public JPackageCommand setTo(JPackageCommand cmd) {
applyTo(cmd::setArgumentValue);
return sign(cmd);
}
private void applyTo(BiConsumer<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;
}
}
}
}
throw new AssertionError();
}
}
static void verifyUnsignedBundleSignature(JPackageCommand cmd) {
if (!cmd.isImagePackageType()) {
MacSignVerify.assertUnsigned(cmd.outputBundle());
}
final Path bundleRoot;
if (cmd.isImagePackageType()) {
bundleRoot = cmd.outputBundle();
} else {
bundleRoot = cmd.pathToUnpackedPackageFile(cmd.appInstallationDirectory());
}
MacSignVerify.assertAdhocSigned(bundleRoot);
}
static PackageHandlers createDmgPackageHandlers() {
return new PackageHandlers(MacHelper::installDmg, MacHelper::uninstallDmg, MacHelper::unpackDmg);
}

View File

@ -59,6 +59,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.naming.ldap.LdapName;
@ -351,6 +352,52 @@ public final class MacSign {
private String password;
}
public static final class UsageBuilder {
UsageBuilder(Collection<Keychain> keychains) {
this.keychains = List.copyOf(keychains);
}
public void run(Runnable runnable) {
Objects.requireNonNull(runnable);
final Optional<List<Path>> oldKeychains;
if (addToSearchList) {
oldKeychains = Optional.ofNullable(activeKeychainFiles());
Keychain.addToSearchList(keychains);
} else {
oldKeychains = Optional.empty();
}
try {
// Ensure keychains to be used for signing are unlocked.
// When the codesign command operates on a locked keychain in a ssh session
// it emits cryptic "errSecInternalComponent" error without other details.
keychains.forEach(Keychain::unlock);
runnable.run();
} finally {
oldKeychains.ifPresent(restoreKeychains -> {
security("list-keychains", "-d", "user", "-s")
.addArguments(restoreKeychains.stream().map(Path::toString).toList())
.execute();
});
}
}
public UsageBuilder addToSearchList(boolean v) {
addToSearchList = v;
return this;
}
public UsageBuilder addToSearchList() {
return addToSearchList(true);
}
private final Collection<Keychain> keychains;
private boolean addToSearchList;
}
Keychain create() {
final var exec = createExecutor("create-keychain");
final var result = exec.saveOutput().executeWithoutExitCodeCheck();
@ -415,24 +462,12 @@ public final class MacSign {
return certs;
}
public static void addToSearchList(Collection<Keychain> keychains) {
static void addToSearchList(Collection<Keychain> keychains) {
security("list-keychains", "-d", "user", "-s", "login.keychain")
.addArguments(keychains.stream().map(Keychain::name).toList())
.execute();
}
public static void withAddedKeychains(Collection<Keychain> keychains, Runnable runnable) {
final var curKeychains = activeKeychainFiles();
addToSearchList(keychains);
try {
runnable.run();
} finally {
security("list-keychains", "-d", "user", "-s")
.addArguments(curKeychains.stream().map(Path::toString).toList())
.execute();
}
}
private static List<Path> activeKeychainFiles() {
// $ security list-keychains
// "/Users/alexeysemenyuk/Library/Keychains/login.keychain-db"
@ -1037,6 +1072,47 @@ public final class MacSign {
return !missingKeychain && !missingCertificates && !invalidCertificates;
}
public static Keychain.UsageBuilder withKeychains(KeychainWithCertsSpec... keychains) {
return withKeychains(Stream.of(keychains).map(KeychainWithCertsSpec::keychain).toArray(Keychain[]::new));
}
public static Keychain.UsageBuilder withKeychains(Keychain... keychains) {
return new Keychain.UsageBuilder(List.of(keychains));
}
public static void withKeychains(Runnable runnable, Consumer<Keychain.UsageBuilder> mutator, Keychain... keychains) {
Objects.requireNonNull(runnable);
var builder = withKeychains(keychains);
mutator.accept(builder);
builder.run(runnable);
}
public static void withKeychains(Runnable runnable, Keychain... keychains) {
withKeychains(runnable, _ -> {}, keychains);
}
public static void withKeychain(Consumer<Keychain> consumer, Consumer<Keychain.UsageBuilder> mutator, Keychain keychain) {
Objects.requireNonNull(consumer);
withKeychains(() -> {
consumer.accept(keychain);
}, mutator, keychain);
}
public static void withKeychain(Consumer<Keychain> consumer, Keychain keychain) {
withKeychain(consumer, _ -> {}, keychain);
}
public static void withKeychain(Consumer<ResolvedKeychain> consumer, Consumer<Keychain.UsageBuilder> mutator, ResolvedKeychain keychain) {
Objects.requireNonNull(consumer);
withKeychains(() -> {
consumer.accept(keychain);
}, mutator, keychain.spec().keychain());
}
public static void withKeychain(Consumer<ResolvedKeychain> consumer, ResolvedKeychain keychain) {
withKeychain(consumer, _ -> {}, keychain);
}
public static final class ResolvedKeychain {
public ResolvedKeychain(KeychainWithCertsSpec spec) {
this.spec = Objects.requireNonNull(spec);
@ -1046,6 +1122,10 @@ public final class MacSign {
return spec;
}
public String name() {
return spec.keychain().name();
}
public Map<CertificateRequest, X509Certificate> mapCertificateRequests() {
if (certMap == null) {
synchronized (this) {

View File

@ -44,6 +44,43 @@ import jdk.jpackage.test.MacSign.CertificateRequest;
*/
public final class MacSignVerify {
public static void verifyAppImageSigned(
JPackageCommand cmd, CertificateRequest certRequest, MacSign.ResolvedKeychain keychain) {
cmd.verifyIsOfType(PackageType.MAC);
Objects.requireNonNull(certRequest);
Objects.requireNonNull(keychain);
final Path bundleRoot;
if (cmd.isImagePackageType()) {
bundleRoot = cmd.outputBundle();
} else {
bundleRoot = cmd.pathToUnpackedPackageFile(
cmd.appInstallationDirectory());
}
assertSigned(bundleRoot, certRequest);
if (!cmd.isRuntime()) {
cmd.addLauncherNames().stream().map(cmd::appLauncherPath).forEach(launcherPath -> {
assertSigned(launcherPath, certRequest);
});
}
// Set to "null" if the sign origin is not found, instead of bailing out with an exception.
// Let is fail in the following TKit.assertEquals() call with a proper log message.
var signOrigin = findSpctlSignOrigin(SpctlType.EXEC, bundleRoot).orElse(null);
TKit.assertEquals(certRequest.name(), signOrigin,
String.format("Check [%s] has sign origin as expected", bundleRoot));
}
public static void verifyPkgSigned(JPackageCommand cmd, CertificateRequest certRequest, MacSign.ResolvedKeychain keychain) {
cmd.verifyIsOfType(PackageType.MAC_PKG);
assertPkgSigned(cmd.outputBundle(), certRequest,
Objects.requireNonNull(keychain.mapCertificateRequests().get(certRequest)));
}
public static void assertSigned(Path path, CertificateRequest certRequest) {
assertSigned(path);
TKit.assertEquals(certRequest.name(), findCodesignSignOrigin(path).orElse(null),
@ -114,8 +151,8 @@ public final class MacSignVerify {
}
public static Optional<String> findCodesignSignOrigin(Path path) {
final var exec = Executor.of("/usr/bin/codesign", "--display", "--verbose=4", path.toString()).saveOutput();
final var result = exec.executeWithoutExitCodeCheck();
final var exec = Executor.of("/usr/bin/codesign", "--display", "--verbose=4", path.toString());
final var result = exec.saveOutput().executeWithoutExitCodeCheck();
if (result.getExitCode() == 0) {
return Optional.of(result.getOutput().stream().map(line -> {
if (line.equals("Signature=adhoc")) {
@ -144,12 +181,34 @@ public final class MacSignVerify {
}
public static void assertSigned(Path path) {
final var verifier = TKit.TextStreamVerifier.group()
.add(TKit.assertTextStream(": valid on disk").predicate(String::endsWith))
.add(TKit.assertTextStream(": satisfies its Designated Requirement").predicate(String::endsWith))
.create();
verifier.accept(Executor.of("/usr/bin/codesign", "--verify", "--deep",
"--strict", "--verbose=2", path.toString()).executeAndGetOutput().iterator());
assertSigned(path, false);
}
private static void assertSigned(Path path, boolean sudo) {
final Executor exec;
if (sudo) {
exec = Executor.of("sudo", "/usr/bin/codesign");
} else {
exec = Executor.of("/usr/bin/codesign");
}
exec.addArguments("--verify", "--deep", "--strict", "--verbose=2", path.toString());
final var result = exec.saveOutput().executeWithoutExitCodeCheck();
if (result.getExitCode() == 0) {
TKit.TextStreamVerifier.group()
.add(TKit.assertTextStream(": valid on disk").predicate(String::endsWith))
.add(TKit.assertTextStream(": satisfies its Designated Requirement").predicate(String::endsWith))
.create().accept(result.getOutput().iterator());
} else if (!sudo && result.getOutput().stream().findFirst().filter(str -> {
// By some reason /usr/bin/codesign command fails for some installed bundles.
// It is known to fail for some AppContentTest test cases and all FileAssociationsTest test cases.
// Rerunning the command with "sudo" works, though.
return str.equals(String.format("%s: Permission denied", path));
}).isPresent()) {
TKit.trace("Try /usr/bin/codesign again with `sudo`");
assertSigned(path, true);
} else {
reportUnexpectedCommandOutcome(exec.getPrintableCommandLine(), result);
}
}
public static List<SignIdentity> getPkgCertificateChain(Path path) {

View File

@ -70,14 +70,14 @@ public class MacSignTest {
final List<CannedFormattedString> expectedStrings = new ArrayList<>();
expectedStrings.add(JPackageStringBundle.MAIN.cannedFormattedString("message.codesign.failed.reason.app.content"));
expectedStrings.add(JPackageStringBundle.MAIN.cannedFormattedString("error.tool.failed.with.output", "codesign"));
final var xcodeWarning = JPackageStringBundle.MAIN.cannedFormattedString("message.codesign.failed.reason.xcode.tools");
if (!MacHelper.isXcodeDevToolsInstalled()) {
expectedStrings.add(xcodeWarning);
}
final var keychain = SigningBase.StandardKeychain.EXPIRED.spec().keychain();
MacSign.Keychain.withAddedKeychains(List.of(keychain), () -> {
MacSign.withKeychain(keychain -> {
// --app-content and --type app-image
// Expect `message.codesign.failed.reason.app.content` message in the log.
// This is not a fatal error, just a warning.
@ -86,8 +86,6 @@ public class MacSignTest {
.ignoreDefaultVerbose(true)
.validateOutput(expectedStrings.toArray(CannedFormattedString[]::new))
.addArguments("--app-content", appContent)
.addArguments("--mac-sign")
.addArguments("--mac-signing-keychain", keychain.name())
.addArguments("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN.spec().name());
if (MacHelper.isXcodeDevToolsInstalled()) {
@ -95,8 +93,36 @@ public class MacSignTest {
cmd.validateOutput(TKit.assertTextStream(xcodeWarning.getValue()).negate());
}
cmd.execute(1);
});
MacHelper.useKeychain(cmd, keychain).execute(1);
}, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain());
}
@Test
public static void testCodesignUnspecifiedFailure() throws IOException {
var appImageCmd = JPackageCommand.helloAppImage().setFakeRuntime();
appImageCmd.executeIgnoreExitCode().assertExitCodeIsZero();
// This test expects jpackage to respond in a specific way on a codesign failure.
// The simplest option to trigger codesign failure is to request the signing of an invalid bundle.
// Create app content directory with the name known to fail signing.
final var appContent = appImageCmd.appLayout().contentDirectory().resolve("foo.1");
Files.createDirectory(appContent);
Files.createFile(appContent.resolve("file"));
final List<CannedFormattedString> expectedStrings = new ArrayList<>();
expectedStrings.add(JPackageStringBundle.MAIN.cannedFormattedString("error.tool.failed.with.output", "codesign"));
MacSign.withKeychain(keychain -> {
final var cmd = new JPackageCommand().setPackageType(PackageType.IMAGE)
.ignoreDefaultVerbose(true)
.validateOutput(expectedStrings.toArray(CannedFormattedString[]::new))
.addArguments("--app-image", appImageCmd.outputBundle())
.addArguments("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN.spec().name());
MacHelper.useKeychain(cmd, keychain).execute(1);
}, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain());
}
@Test
@ -116,20 +142,16 @@ public class MacSignTest {
@Parameter({"MAC_PKG", "EXPIRED_CODESIGN_SIGN_IDENTITY", "GOOD_PKG_SIGN_IDENTITY"})
public static void testExpiredCertificate(PackageType type, SignOption... options) {
final var keychain = SigningBase.StandardKeychain.EXPIRED.spec().keychain();
MacSign.Keychain.withAddedKeychains(List.of(keychain), () -> {
final var cmd = JPackageCommand.helloAppImage()
MacSign.withKeychain(keychain -> {
final var cmd = MacHelper.useKeychain(JPackageCommand.helloAppImage(), keychain)
.ignoreDefaultVerbose(true)
.addArguments("--mac-sign")
.addArguments("--mac-signing-keychain", keychain.name())
.addArguments(Stream.of(options).map(SignOption::args).flatMap(List::stream).toList())
.setPackageType(type);
SignOption.configureOutputValidation(cmd, Stream.of(options).filter(SignOption::expired).toList(), opt -> {
return JPackageStringBundle.MAIN.cannedFormattedString("error.certificate.expired", opt.identityName());
}).execute(1);
});
}, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.EXPIRED.keychain());
}
@Test
@ -148,39 +170,31 @@ public class MacSignTest {
@Parameter({"MAC_PKG", "1", "GOOD_PKG_SIGN_IDENTITY"})
public static void testMultipleCertificates(PackageType type, int jpackageExitCode, SignOption... options) {
final var keychain = SigningBase.StandardKeychain.DUPLICATE.spec().keychain();
MacSign.Keychain.withAddedKeychains(List.of(keychain), () -> {
final var cmd = JPackageCommand.helloAppImage()
MacSign.withKeychain(keychain -> {
final var cmd = MacHelper.useKeychain(JPackageCommand.helloAppImage(), keychain)
.ignoreDefaultVerbose(true)
.addArguments("--mac-sign")
.addArguments("--mac-signing-keychain", keychain.name())
.addArguments(Stream.of(options).map(SignOption::args).flatMap(List::stream).toList())
.setPackageType(type);
SignOption.configureOutputValidation(cmd, List.of(options), opt -> {
return JPackageStringBundle.MAIN.cannedFormattedString("error.multiple.certs.found", opt.identityName(), keychain.name());
}).execute(jpackageExitCode);
});
}, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.DUPLICATE.keychain());
}
@Test
@ParameterSupplier
public static void testSelectSigningIdentity(String signingKeyUserName, CertificateRequest certRequest) {
final var keychain = SigningBase.StandardKeychain.MAIN.spec().keychain();
MacSign.Keychain.withAddedKeychains(List.of(keychain), () -> {
final var cmd = JPackageCommand.helloAppImage()
MacSign.withKeychain(keychain -> {
final var cmd = MacHelper.useKeychain(JPackageCommand.helloAppImage(), keychain)
.setFakeRuntime()
.addArguments("--mac-sign")
.addArguments("--mac-signing-keychain", keychain.name())
.addArguments("--mac-signing-key-user-name", signingKeyUserName);
cmd.executeAndAssertHelloAppImageCreated();
MacSignVerify.assertSigned(cmd.outputBundle(), certRequest);
});
}, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain());
}
public static Collection<Object[]> testSelectSigningIdentity() {

View File

@ -21,12 +21,14 @@
* questions.
*/
import java.nio.file.Path;
import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Annotations.Parameter;
import java.nio.file.Path;
import jdk.jpackage.test.AdditionalLauncher;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.MacSign;
/**
* Tests generation of app image with --mac-sign and related arguments. Test will
@ -68,13 +70,19 @@ public class SigningAppImageTest {
// Unsigned
@Parameter({"false", "true", "INVALID_INDEX"})
public void test(boolean doSign, boolean signingKey, SigningBase.CertIndex certEnum) throws Exception {
MacSign.withKeychain(toConsumer(keychain -> {
test(keychain, doSign, signingKey, certEnum);
}), SigningBase.StandardKeychain.MAIN.keychain());
}
private void test(MacSign.ResolvedKeychain keychain, boolean doSign, boolean signingKey, SigningBase.CertIndex certEnum) throws Exception {
final var certIndex = certEnum.value();
JPackageCommand cmd = JPackageCommand.helloAppImage();
if (doSign) {
cmd.addArguments("--mac-sign",
"--mac-signing-keychain",
SigningBase.getKeyChain());
keychain.name());
if (signingKey) {
cmd.addArguments("--mac-signing-key-user-name",
SigningBase.getDevName(certIndex));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -21,14 +21,16 @@
* questions.
*/
import java.nio.file.Path;
import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Annotations.Parameter;
import java.nio.file.Path;
import jdk.jpackage.test.AdditionalLauncher;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.MacSign;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.TKit;
/**
* Tests generation of app image and then signs generated app image with --mac-sign
@ -67,6 +69,12 @@ public class SigningAppImageTwoStepsTest {
// Unsigned
@Parameter({"false", "true"})
public void test(boolean signAppImage, boolean signingKey) throws Exception {
MacSign.withKeychain(toConsumer(keychain -> {
test(keychain, signAppImage, signingKey);
}), SigningBase.StandardKeychain.MAIN.keychain());
}
private static void test(MacSign.ResolvedKeychain keychain, boolean signAppImage, boolean signingKey) throws Exception {
Path appimageOutput = TKit.createTempDirectory("appimage");
@ -78,7 +86,7 @@ public class SigningAppImageTwoStepsTest {
if (signAppImage) {
appImageCmd.addArguments("--mac-sign",
"--mac-signing-keychain",
SigningBase.getKeyChain());
keychain.name());
if (signingKey) {
appImageCmd.addArguments("--mac-signing-key-user-name",
SigningBase.getDevName(SigningBase.DEFAULT_INDEX));
@ -103,7 +111,7 @@ public class SigningAppImageTwoStepsTest {
cmd.setPackageType(PackageType.IMAGE)
.addArguments("--app-image", appImageCmd.outputBundle().toAbsolutePath())
.addArguments("--mac-sign")
.addArguments("--mac-signing-keychain", SigningBase.getKeyChain());
.addArguments("--mac-signing-keychain", keychain.name());
if (signingKey) {
cmd.addArguments("--mac-signing-key-user-name",
SigningBase.getDevName(SigningBase.DEFAULT_INDEX));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -21,15 +21,18 @@
* questions.
*/
import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer;
import java.nio.file.Path;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.ApplicationLayout;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.MacHelper;
import jdk.jpackage.test.MacSign;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.MacHelper;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.TKit;
/**
* Tests generation of dmg and pkg from signed predefined app image which was
@ -102,6 +105,12 @@ public class SigningPackageFromTwoStepAppImageTest {
// Unsigned
@Parameter({"false", "true"})
public void test(boolean signAppImage, boolean signingKey) throws Exception {
MacSign.withKeychain(toConsumer(keychain -> {
test(keychain, signAppImage, signingKey);
}), SigningBase.StandardKeychain.MAIN.keychain());
}
private void test(MacSign.ResolvedKeychain keychain, boolean signAppImage, boolean signingKey) throws Exception {
Path appimageOutput = TKit.createTempDirectory("appimage");
@ -112,7 +121,7 @@ public class SigningPackageFromTwoStepAppImageTest {
.setArgumentValue("--dest", appimageOutput);
if (signAppImage) {
appImageCmd.addArguments("--mac-sign",
"--mac-signing-keychain", SigningBase.getKeyChain());
"--mac-signing-keychain", keychain.name());
if (signingKey) {
appImageCmd.addArguments("--mac-signing-key-user-name",
SigningBase.getDevName(SigningBase.DEFAULT_INDEX));
@ -133,7 +142,7 @@ public class SigningPackageFromTwoStepAppImageTest {
appImageSignedCmd.setPackageType(PackageType.IMAGE)
.addArguments("--app-image", appImageCmd.outputBundle().toAbsolutePath())
.addArguments("--mac-sign")
.addArguments("--mac-signing-keychain", SigningBase.getKeyChain());
.addArguments("--mac-signing-keychain", keychain.name());
if (signingKey) {
appImageSignedCmd.addArguments("--mac-signing-key-user-name",
SigningBase.getDevName(SigningBase.DEFAULT_INDEX));
@ -154,7 +163,7 @@ public class SigningPackageFromTwoStepAppImageTest {
if (signAppImage) {
cmd.addArguments("--mac-sign",
"--mac-signing-keychain",
SigningBase.getKeyChain());
keychain.name());
if (signingKey) {
cmd.addArguments("--mac-signing-key-user-name",
SigningBase.getDevName(SigningBase.DEFAULT_INDEX));

View File

@ -21,14 +21,17 @@
* questions.
*/
import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer;
import java.nio.file.Path;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.ApplicationLayout;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.MacHelper;
import jdk.jpackage.test.MacSign;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.MacHelper;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Annotations.Parameter;
/**
* Tests generation of dmg and pkg with --mac-sign and related arguments.
@ -144,6 +147,12 @@ public class SigningPackageTest {
// Signing-indentity, but sign pkg only and UNICODE certificate
@Parameter({"false", "false", "true", "UNICODE_INDEX"})
public static void test(boolean signingKey, boolean signAppImage, boolean signPKG, SigningBase.CertIndex certEnum) throws Exception {
MacSign.withKeychain(toConsumer(keychain -> {
test(keychain, signingKey, signAppImage, signPKG, certEnum);
}), SigningBase.StandardKeychain.MAIN.keychain());
}
private static void test(MacSign.ResolvedKeychain keychain, boolean signingKey, boolean signAppImage, boolean signPKG, SigningBase.CertIndex certEnum) throws Exception {
final var certIndex = certEnum.value();
new PackageTest()
@ -151,7 +160,7 @@ public class SigningPackageTest {
.forTypes(PackageType.MAC)
.addInitializer(cmd -> {
cmd.addArguments("--mac-sign",
"--mac-signing-keychain", SigningBase.getKeyChain());
"--mac-signing-keychain", keychain.name());
if (signingKey) {
cmd.addArguments("--mac-signing-key-user-name",
SigningBase.getDevName(certIndex));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -21,31 +21,39 @@
* questions.
*/
import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer;
import java.nio.file.Path;
import jdk.jpackage.test.ApplicationLayout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Stream;
import jdk.jpackage.test.Annotations.ParameterSupplier;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.JPackageStringBundle;
import jdk.jpackage.test.MacHelper;
import jdk.jpackage.test.MacHelper.SignKeyOption;
import jdk.jpackage.test.MacSign;
import jdk.jpackage.test.MacSignVerify;
import jdk.jpackage.test.PackageFile;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.MacHelper;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.TKit;
/**
* Note: Testing unsgined app image is done to verify support for per-user
* configuration by checking for PackageFile.
* Tests generation of dmg and pkg from signed or unsigned predefined app image.
* Test will generate pkg and verifies its signature. It verifies that dmg
* is not signed, but app image inside dmg is signed or unsigned. This test
* requires that the machine is configured with test certificate for
* "Developer ID Installer: jpackage.openjdk.java.net" in
* jpackagerTest keychain with
* always allowed access to this keychain for user which runs test.
* note:
* "jpackage.openjdk.java.net" can be over-ridden by system property
* "jpackage.mac.signing.key.user.name", and
* "jpackagerTest" can be over-ridden by system property
* "jpackage.mac.signing.keychain"
* Tests packaging of a signed/unsigned predefined app image into a
* signed/unsigned .pkg or .dmg package.
*
* <p>
* Prerequisites: A keychain with self-signed certificates as specified in
* {@link SigningBase.StandardKeychain#MAIN}.
*/
/*
@ -64,100 +72,180 @@ import jdk.jpackage.test.Annotations.Parameter;
*/
public class SigningPackageTwoStepTest {
private static void verifyPKG(JPackageCommand cmd) {
if (!cmd.hasArgument("--mac-sign")) {
return; // Nothing to check if not signed
}
Path outputBundle = cmd.outputBundle();
SigningBase.verifyPkgutil(outputBundle, true, SigningBase.DEFAULT_INDEX);
SigningBase.verifySpctl(outputBundle, "install", SigningBase.DEFAULT_INDEX);
}
private static void verifyDMG(JPackageCommand cmd) {
// DMG always unsigned, so we will check it
Path outputBundle = cmd.outputBundle();
SigningBase.verifyDMG(outputBundle);
}
private static void verifyAppImageInDMG(JPackageCommand cmd) {
MacHelper.withExplodedDmg(cmd, dmgImage -> {
// We will be called with all folders in DMG since JDK-8263155, but
// we only need to verify app.
if (dmgImage.endsWith(cmd.name() + ".app")) {
boolean isSigned = cmd.hasArgument("--mac-sign");
Path launcherPath = ApplicationLayout.platformAppImage()
.resolveAt(dmgImage).launchersDirectory().resolve(cmd.name());
SigningBase.verifyCodesign(launcherPath, isSigned, SigningBase.DEFAULT_INDEX);
SigningBase.verifyCodesign(dmgImage, isSigned, SigningBase.DEFAULT_INDEX);
if (isSigned) {
SigningBase.verifySpctl(dmgImage, "exec", SigningBase.DEFAULT_INDEX);
}
}
});
}
@Test
// (Signed, "signing-key or sign-identity"})
// Signed and signing-key
@Parameter({"true", "true"})
// Signed and signing-identity
@Parameter({"true", "false"})
// Unsigned
@Parameter({"false", "true"})
public static void test(boolean signAppImage, boolean signingKey) throws Exception {
Path appimageOutput = TKit.createTempDirectory("appimage");
@ParameterSupplier
public static void test(TestSpec spec) {
MacSign.withKeychain(toConsumer(keychain -> {
spec.test(keychain);
}), SigningBase.StandardKeychain.MAIN.keychain());
}
JPackageCommand appImageCmd = JPackageCommand.helloAppImage()
.setArgumentValue("--dest", appimageOutput);
if (signAppImage) {
appImageCmd.addArguments("--mac-sign")
.addArguments("--mac-signing-keychain",
SigningBase.getKeyChain());
if (signingKey) {
appImageCmd.addArguments("--mac-signing-key-user-name",
SigningBase.getDevName(SigningBase.DEFAULT_INDEX));
} else {
appImageCmd.addArguments("--mac-app-image-sign-identity",
SigningBase.getAppCert(SigningBase.DEFAULT_INDEX));
public record TestSpec(Optional<SignKeyOption> signAppImage, Map<PackageType, SignKeyOption> signPackage) {
public TestSpec {
Objects.requireNonNull(signAppImage);
Objects.requireNonNull(signPackage);
if ((signAppImage.isEmpty() && signPackage.isEmpty()) || !PackageType.MAC.containsAll(signPackage.keySet())) {
// Unexpected package types.
throw new IllegalArgumentException();
}
// Ensure stable result of toString() call.
if (!SortedMap.class.isInstance(signPackage)) {
signPackage = new TreeMap<>(signPackage);
}
}
new PackageTest()
.addRunOnceInitializer(() -> appImageCmd.execute())
.forTypes(PackageType.MAC)
.addInitializer(cmd -> {
cmd.addArguments("--app-image", appImageCmd.outputBundle());
cmd.removeArgumentWithValue("--input");
if (signAppImage) {
cmd.addArguments("--mac-sign",
"--mac-signing-keychain",
SigningBase.getKeyChain());
if (signingKey) {
cmd.addArguments("--mac-signing-key-user-name",
SigningBase.getDevName(SigningBase.DEFAULT_INDEX));
} else {
cmd.addArguments("--mac-installer-sign-identity",
SigningBase.getInstallerCert(SigningBase.DEFAULT_INDEX));
}
}
})
.forTypes(PackageType.MAC_PKG)
.addBundleVerifier(SigningPackageTwoStepTest::verifyPKG)
.forTypes(PackageType.MAC_DMG)
.addInitializer(cmd -> {
if (signAppImage && !signingKey) {
// jpackage throws expected error with
// --mac-installer-sign-identity and DMG type
cmd.removeArgumentWithValue("--mac-installer-sign-identity");
// It will do nothing, but it signals test that app
// image itself is signed for verification.
cmd.addArguments("--mac-app-image-sign-identity",
SigningBase.getAppCert(SigningBase.DEFAULT_INDEX));
}
})
.addBundleVerifier(SigningPackageTwoStepTest::verifyDMG)
.addBundleVerifier(SigningPackageTwoStepTest::verifyAppImageInDMG)
.run();
@Override
public String toString() {
var sb = new StringBuilder();
signAppImage.ifPresent(signOption -> {
sb.append(String.format("app-image=%s", signOption));
});
if (!sb.isEmpty() && !signPackage.isEmpty()) {
sb.append("; ");
}
if (!signPackage.isEmpty()) {
sb.append(signPackage);
}
return sb.toString();
}
boolean signNativeBundle() {
return signPackage.isEmpty();
}
static Builder build() {
return new Builder();
}
static class Builder {
TestSpec create() {
return new TestSpec(Optional.ofNullable(signAppImage), signPackage);
}
Builder certRequest(SigningBase.StandardCertificateRequest v) {
return certRequest(v.spec());
}
Builder certRequest(MacSign.CertificateRequest v) {
certRequest = Objects.requireNonNull(v);
return this;
}
Builder signIdentityType(SignKeyOption.Type v) {
signIdentityType = Objects.requireNonNull(v);
return this;
}
Builder signAppImage() {
signAppImage = createSignKeyOption();
return this;
}
Builder signPackage(PackageType type) {
Objects.requireNonNull(type);
signPackage.put(type, createSignKeyOption());
return this;
}
Builder signPackage() {
PackageType.MAC.forEach(this::signPackage);
return this;
}
private SignKeyOption createSignKeyOption() {
return new SignKeyOption(signIdentityType, certRequest);
}
private MacSign.CertificateRequest certRequest = SigningBase.StandardCertificateRequest.CODESIGN.spec();
private SignKeyOption.Type signIdentityType = SignKeyOption.Type.SIGN_KEY_IDENTITY;
private SignKeyOption signAppImage;
private Map<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);
}).addInitializer(cmd -> {
MacHelper.useKeychain(cmd, keychain);
cmd.addArguments("--app-image", appImageCmd.outputBundle());
cmd.removeArgumentWithValue("--input");
Optional.ofNullable(signPackage.get(cmd.packageType())).ifPresent(signOption -> {
signOption.setTo(cmd);
});
if (signAppImage.isPresent()) {
// Predefined app image is signed. Expect a warning.
cmd.validateOutput(JPackageStringBundle.MAIN.cannedFormattedString(
"warning.per.user.app.image.signed",
PackageFile.getPathInAppImage(Path.of(""))));
} else if (cmd.packageType() == PackageType.MAC_PKG && signPackage.containsKey(cmd.packageType())) {
// Create signed ".pkg" bundle from the unsigned predefined app image. Expect a warning.
cmd.validateOutput(JPackageStringBundle.MAIN.cannedFormattedString("warning.unsigned.app.image", "pkg"));
}
})
.run();
}
}
public static Collection<Object[]> test() {
List<TestSpec.Builder> data = new ArrayList<>();
Stream.of(SignKeyOption.Type.values()).flatMap(signIdentityType -> {
return Stream.of(
// Sign both predefined app image and native package.
TestSpec.build().signIdentityType(signIdentityType)
.signAppImage()
.signPackage()
.certRequest(SigningBase.StandardCertificateRequest.PKG)
.signPackage(PackageType.MAC_PKG),
// Don't sign predefined app image, sign native package.
TestSpec.build().signIdentityType(signIdentityType)
.signPackage()
.certRequest(SigningBase.StandardCertificateRequest.PKG)
.signPackage(PackageType.MAC_PKG),
// Sign predefined app image, don't sign native package.
TestSpec.build().signIdentityType(signIdentityType).signAppImage()
);
}).forEach(data::add);
return data.stream().map(TestSpec.Builder::create).map(v -> {
return new Object[] {v};
}).toList();
}
}

View File

@ -21,51 +21,53 @@
* questions.
*/
import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer;
import java.io.IOException;
import java.nio.file.Path;
import java.util.function.Predicate;
import java.util.stream.Stream;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Executor;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.MacHelper;
import jdk.jpackage.test.MacSign;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.TKit;
/**
* Tests generation of dmg and pkg with --mac-sign and related arguments.
* Test will generate pkg and verifies its signature. It verifies that dmg
* is not signed, but runtime image inside dmg is signed.
* Tests generation of dmg and pkg with --mac-sign and related arguments. Test
* will generate pkg and verifies its signature. It verifies that dmg is not
* signed, but runtime image inside dmg is signed.
*
* Note: Specific UNICODE signing is not tested, since it is shared code
* with app image signing and it will be covered by SigningPackageTest.
* <p>
* Note: Specific UNICODE signing is not tested, since it is shared code with
* app image signing and it will be covered by SigningPackageTest.
*
* <p>
* Following combinations are tested:
* 1) "--runtime-image" points to unsigned JDK bundle and --mac-sign is not
* <ol>
* <li>"--runtime-image" points to unsigned JDK bundle and --mac-sign is not
* provided. Expected result: runtime image ad-hoc signed.
* 2) "--runtime-image" points to unsigned JDK bundle and --mac-sign is
* <li>"--runtime-image" points to unsigned JDK bundle and --mac-sign is
* provided. Expected result: Everything is signed with provided certificate.
* 3) "--runtime-image" points to signed JDK bundle and --mac-sign is not
* <li>"--runtime-image" points to signed JDK bundle and --mac-sign is not
* provided. Expected result: runtime image is signed with original certificate.
* 4) "--runtime-image" points to signed JDK bundle and --mac-sign is provided.
* <li>"--runtime-image" points to signed JDK bundle and --mac-sign is provided.
* Expected result: runtime image is signed with provided certificate.
* 5) "--runtime-image" points to JDK image and --mac-sign is not provided.
* <li>"--runtime-image" points to JDK image and --mac-sign is not provided.
* Expected result: runtime image ad-hoc signed.
* 6) "--runtime-image" points to JDK image and --mac-sign is provided.
* <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:
* "Developer ID Installer: jpackage.openjdk.java.net" in jpackagerTest keychain
* with always allowed access to this keychain for user which runs test. note:
* "jpackage.openjdk.java.net" can be over-ridden by system property
* "jpackage.mac.signing.key.user.name", and
* "jpackagerTest" can be over-ridden by system property
* "jpackage.mac.signing.keychain"
* "jpackage.mac.signing.key.user.name"
*/
/*
@ -84,17 +86,17 @@ import jdk.jpackage.test.TKit;
*/
public class SigningRuntimeImagePackageTest {
private static JPackageCommand addSignOptions(JPackageCommand cmd, int certIndex) {
private static JPackageCommand addSignOptions(JPackageCommand cmd, MacSign.ResolvedKeychain keychain, int certIndex) {
if (certIndex != SigningBase.CertIndex.INVALID_INDEX.value()) {
cmd.addArguments(
"--mac-sign",
"--mac-signing-keychain", SigningBase.getKeyChain(),
"--mac-signing-keychain", keychain.name(),
"--mac-signing-key-user-name", SigningBase.getDevName(certIndex));
}
return cmd;
}
private static Path createInputRuntimeBundle(int certIndex) throws IOException {
private static Path createInputRuntimeBundle(MacSign.ResolvedKeychain keychain, int certIndex) throws IOException {
final var runtimeImage = JPackageCommand.createInputRuntimeImage();
@ -111,7 +113,7 @@ public class SigningRuntimeImagePackageTest {
.addArguments("--runtime-image", runtimeImage)
.addArguments("--dest", runtimeBundleWorkDir);
addSignOptions(cmd, certIndex);
addSignOptions(cmd, keychain, certIndex);
cmd.execute();
@ -147,13 +149,21 @@ public class SigningRuntimeImagePackageTest {
public static void test(boolean useJDKBundle,
SigningBase.CertIndex jdkBundleCert,
SigningBase.CertIndex signCert) throws Exception {
MacSign.withKeychain(toConsumer(keychain -> {
test(keychain, useJDKBundle, jdkBundleCert, signCert);
}), SigningBase.StandardKeychain.MAIN.keychain());
}
private static void test(MacSign.ResolvedKeychain keychain, boolean useJDKBundle,
SigningBase.CertIndex jdkBundleCert,
SigningBase.CertIndex signCert) throws Exception {
final Path inputRuntime[] = new Path[1];
new PackageTest()
.addRunOnceInitializer(() -> {
if (useJDKBundle) {
inputRuntime[0] = createInputRuntimeBundle(jdkBundleCert.value());
inputRuntime[0] = createInputRuntimeBundle(keychain, jdkBundleCert.value());
} else {
inputRuntime[0] = JPackageCommand.createInputRuntimeImage();
}
@ -164,7 +174,7 @@ public class SigningRuntimeImagePackageTest {
// create input directory in the test and jpackage fails
// if --input references non existent directory.
cmd.removeArgumentWithValue("--input");
addSignOptions(cmd, signCert.value());
addSignOptions(cmd, keychain, signCert.value());
})
.addInstallVerifier(cmd -> {
final var certIndex = Stream.of(signCert, jdkBundleCert)

View File

@ -90,17 +90,29 @@ public class SigningBase {
private final CertificateRequest spec;
}
/**
* Standard keychains used in signing tests.
*/
public enum StandardKeychain {
MAIN(DEFAULT_KEYCHAIN,
/**
* The primary keychain with good certificates.
*/
MAIN("jpackagerTest.keychain",
StandardCertificateRequest.CODESIGN,
StandardCertificateRequest.PKG,
StandardCertificateRequest.CODESIGN_UNICODE,
StandardCertificateRequest.PKG_UNICODE),
/**
* A keychain with some good and some expired certificates.
*/
EXPIRED("jpackagerTest-expired.keychain",
StandardCertificateRequest.CODESIGN,
StandardCertificateRequest.PKG,
StandardCertificateRequest.CODESIGN_EXPIRED,
StandardCertificateRequest.PKG_EXPIRED),
/**
* A keychain with duplicated certificates.
*/
DUPLICATE("jpackagerTest-duplicate.keychain",
StandardCertificateRequest.CODESIGN,
StandardCertificateRequest.PKG,
@ -114,30 +126,26 @@ public class SigningBase {
StandardKeychain(String keychainName, CertificateRequest cert, CertificateRequest... otherCerts) {
final var builder = keychain(keychainName).addCert(cert);
List.of(otherCerts).forEach(builder::addCert);
this.spec = new ResolvedKeychain(builder.create());
this.keychain = new ResolvedKeychain(builder.create());
}
public KeychainWithCertsSpec spec() {
return spec.spec();
public ResolvedKeychain keychain() {
return keychain;
}
public X509Certificate mapCertificateRequest(CertificateRequest certRequest) {
return Objects.requireNonNull(spec.mapCertificateRequests().get(certRequest));
return Objects.requireNonNull(keychain.mapCertificateRequests().get(certRequest));
}
private static KeychainWithCertsSpec.Builder keychain(String name) {
return new KeychainWithCertsSpec.Builder().name(name);
}
private static CertificateRequest.Builder cert() {
return new CertificateRequest.Builder();
}
private static List<KeychainWithCertsSpec> signingEnv() {
return Stream.of(values()).map(StandardKeychain::spec).toList();
return Stream.of(values()).map(StandardKeychain::keychain).map(ResolvedKeychain::spec).toList();
}
private final ResolvedKeychain spec;
private final ResolvedKeychain keychain;
}
public static void setUp() {
@ -179,7 +187,6 @@ public class SigningBase {
"jpackage.openjdk.java.net",
"jpackage.openjdk.java.net (ö)",
};
private static String DEFAULT_KEYCHAIN = "jpackagerTest.keychain";
public static String getDevName(int certIndex) {
// Always use values from system properties if set
@ -195,16 +202,6 @@ public class SigningBase {
return Arrays.binarySearch(DEV_NAMES, devName);
}
// Returns 'true' if dev name from DEV_NAMES
public static boolean isDevNameDefault() {
String value = System.getProperty("jpackage.mac.signing.key.user.name");
if (value != null) {
return false;
}
return true;
}
public static String getAppCert(int certIndex) {
return "Developer ID Application: " + getDevName(certIndex);
}
@ -213,16 +210,6 @@ public class SigningBase {
return "Developer ID Installer: " + getDevName(certIndex);
}
public static String getKeyChain() {
// Always use values from system properties if set
String value = System.getProperty("jpackage.mac.signing.keychain");
if (value != null) {
return value;
}
return DEFAULT_KEYCHAIN;
}
public static void verifyCodesign(Path target, boolean signed, int certIndex) {
if (signed) {
final var certRequest = getCertRequest(certIndex);