mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8375242: [macos] Improve jpackage signing coverage
Reviewed-by: almatvee
This commit is contained in:
parent
e7432d5745
commit
9b47c23b4b
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
178
test/jdk/tools/jpackage/macosx/SigningBase.java
Normal file
178
test/jdk/tools/jpackage/macosx/SigningBase.java
Normal 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 (ö)";
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user