From 33c5e1269aea741f743d5c2597c9d5c57f3ad357 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 9 Oct 2023 18:37:10 -0700 Subject: [PATCH] Write signature files to uber jars to for Oracle Java 17 verification Update Gradle and Maven plugins to write an empty `META-INF/BOOT.SF` file whenever there is a nested signed jar. This update allows Oracle Java 17 to correctly verify the nested JARs. The file is required because `JarVerifier` has code roughly equivalent to: if (!jarManifestNameChecked && SharedSecrets .getJavaUtilZipFileAccess().getManifestName(jf, true) == null) { throw new JarException("The JCE Provider " + jarURL.toString() + " is not signed."); } The `SharedSecrets.getJavaUtilZipFileAccess().getManifestName(jf, true)` call ends up in `ZipFile.getManifestName(onlyIfSignatureRelatedFiles)` which is a private method that we cannot override in our `NestedJarFile` subclass. By writing an empty `.SF` file we ensure that the `Manifest` is always returned because there are always "signature related files". Fixes gh-28837 --- .../tasks/bundling/BootArchiveSupport.java | 10 +-- .../boot/gradle/tasks/bundling/BootJar.java | 6 +- .../boot/gradle/tasks/bundling/BootWar.java | 6 +- .../tasks/bundling/BootZipCopyAction.java | 22 ++++++- .../bundling/BootJarIntegrationTests.java | 12 ++++ .../BootJarIntegrationTests-signed.gradle | 17 +++++ .../boot/loader/tools/FileUtils.java | 32 +++++++++- .../boot/loader/tools/Packager.java | 5 ++ .../boot/loader/tools/Repackager.java | 21 ++++++- .../loader/tools/AbstractPackagerTests.java | 4 +- .../boot/loader/tools/FileUtilsTests.java | 28 +++++++++ .../boot/loader/tools/RepackagerTests.java | 15 +++++ .../boot/loader/tools/signed-manifest.mf | 9 +++ .../boot/maven/JarIntegrationTests.java | 8 +++ .../src/intTest/projects/jar-signed/pom.xml | 62 +++++++++++++++++++ .../main/java/org/test/SampleApplication.java | 24 +++++++ .../boot/loader/LoaderIntegrationTests.java | 2 - 17 files changed, 266 insertions(+), 17 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-signed.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/resources/org/springframework/boot/loader/tools/signed-manifest.mf create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-signed/pom.xml create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-signed/src/main/java/org/test/SampleApplication.java diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java index 782eb730d2e..330bc1aef1c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java @@ -123,12 +123,13 @@ class BootArchiveSupport { } CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, - LoaderImplementation loaderImplementation) { - return createCopyAction(jar, resolvedDependencies, loaderImplementation, null, null); + LoaderImplementation loaderImplementation, boolean supportsSignatureFile) { + return createCopyAction(jar, resolvedDependencies, loaderImplementation, supportsSignatureFile, null, null); } CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, - LoaderImplementation loaderImplementation, LayerResolver layerResolver, String layerToolsLocation) { + LoaderImplementation loaderImplementation, boolean supportsSignatureFile, LayerResolver layerResolver, + String layerToolsLocation) { File output = jar.getArchiveFile().get().getAsFile(); Manifest manifest = jar.getManifest(); boolean preserveFileTimestamps = jar.isPreserveFileTimestamps(); @@ -143,7 +144,8 @@ class BootArchiveSupport { String encoding = jar.getMetadataCharset(); CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirMode, fileMode, includeDefaultLoader, layerToolsLocation, requiresUnpack, exclusions, launchScript, librarySpec, - compressionResolver, encoding, resolvedDependencies, layerResolver, loaderImplementation); + compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver, + loaderImplementation); return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java index c76a95f1d6c..7ed3f998c54 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java @@ -147,10 +147,10 @@ public abstract class BootJar extends Jar implements BootArchive { if (!isLayeredDisabled()) { LayerResolver layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); String layerToolsLocation = this.layered.getIncludeLayerTools().get() ? LIB_DIRECTORY : null; - return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, layerResolver, - layerToolsLocation); + return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, true, + layerResolver, layerToolsLocation); } - return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation); + return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, true); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java index d3aa0eab860..d19f152f84b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java @@ -121,10 +121,10 @@ public abstract class BootWar extends War implements BootArchive { if (!isLayeredDisabled()) { LayerResolver layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); String layerToolsLocation = this.layered.getIncludeLayerTools().get() ? LIB_DIRECTORY : null; - return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, layerResolver, - layerToolsLocation); + return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, false, + layerResolver, layerToolsLocation); } - return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation); + return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, false); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java index 2d8b53ea73a..85f509ecf76 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java @@ -111,6 +111,8 @@ class BootZipCopyAction implements CopyAction { private final ResolvedDependencies resolvedDependencies; + private final boolean supportsSignatureFile; + private final LayerResolver layerResolver; private final LoaderImplementation loaderImplementation; @@ -119,7 +121,7 @@ class BootZipCopyAction implements CopyAction { boolean includeDefaultLoader, String layerToolsLocation, Spec requiresUnpack, Spec exclusions, LaunchScriptConfiguration launchScript, Spec librarySpec, Function compressionResolver, String encoding, - ResolvedDependencies resolvedDependencies, LayerResolver layerResolver, + ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile, LayerResolver layerResolver, LoaderImplementation loaderImplementation) { this.output = output; this.manifest = manifest; @@ -135,6 +137,7 @@ class BootZipCopyAction implements CopyAction { this.compressionResolver = compressionResolver; this.encoding = encoding; this.resolvedDependencies = resolvedDependencies; + this.supportsSignatureFile = supportsSignatureFile; this.layerResolver = layerResolver; this.loaderImplementation = loaderImplementation; } @@ -302,6 +305,7 @@ class BootZipCopyAction implements CopyAction { void finish() throws IOException { writeLoaderEntriesIfNecessary(null); writeJarToolsIfNecessary(); + writeSignatureFileIfNecessary(); writeClassPathIndexIfNecessary(); writeNativeImageArgFileIfNecessary(); // We must write the layer index last @@ -351,6 +355,22 @@ class BootZipCopyAction implements CopyAction { } } + private void writeSignatureFileIfNecessary() throws IOException { + if (BootZipCopyAction.this.supportsSignatureFile && hasSignedLibrary()) { + writeEntry("META-INF/BOOT.SF", (out) -> { + }, false); + } + } + + private boolean hasSignedLibrary() throws IOException { + for (FileCopyDetails writtenLibrary : this.writtenLibraries.values()) { + if (FileUtils.isSignedJarFile(writtenLibrary.getFile())) { + return true; + } + } + return false; + } + private void writeClassPathIndexIfNecessary() throws IOException { Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes(); String classPathIndex = (String) manifestAttributes.get("Spring-Boot-Classpath-Index"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java index 146fb595ae6..d83e54ed165 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java @@ -16,12 +16,15 @@ package org.springframework.boot.gradle.tasks.bundling; +import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Set; import java.util.TreeSet; +import java.util.jar.JarFile; import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; @@ -42,6 +45,15 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests { super("bootJar", "BOOT-INF/lib/", "BOOT-INF/classes/", "BOOT-INF/"); } + @TestTemplate + void signed() throws Exception { + assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + File jar = new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0]; + try (JarFile jarFile = new JarFile(jar)) { + assertThat(jarFile.getEntry("META-INF/BOOT.SF")).isNotNull(); + } + } + @TestTemplate void whenAResolvableCopyOfAnUnresolvableConfigurationIsResolvedThenResolutionSucceeds() { this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.0").build("build"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-signed.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-signed.gradle new file mode 100644 index 00000000000..e879cc96e8a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-signed.gradle @@ -0,0 +1,17 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' +} + +repositories { + mavenCentral() + maven { url "file:repository" } +} + +dependencies { + implementation("org.bouncycastle:bcprov-jdk18on:1.76") +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/FileUtils.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/FileUtils.java index ecfded73907..0347e1cbe6f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/FileUtils.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/FileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,9 @@ package org.springframework.boot.loader.tools; import java.io.File; import java.io.IOException; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; /** * Utilities for manipulating files and directories in Spring Boot tooling. @@ -61,4 +64,31 @@ public abstract class FileUtils { return Digest.sha1(InputStreamSupplier.forFile(file)); } + /** + * Returns {@code true} if the given jar file has been signed. + * @param file the file to check + * @return if the file has been signed + * @throws IOException on IO error + */ + public static boolean isSignedJarFile(File file) throws IOException { + try (JarFile jarFile = new JarFile(file)) { + if (hasDigestEntry(jarFile.getManifest())) { + return true; + } + } + return false; + } + + private static boolean hasDigestEntry(Manifest manifest) { + return (manifest != null) && manifest.getEntries().values().stream().anyMatch(FileUtils::hasDigestName); + } + + private static boolean hasDigestName(Attributes attributes) { + return attributes.keySet().stream().anyMatch(FileUtils::isDigestName); + } + + private static boolean isDigestName(Object name) { + return String.valueOf(name).toUpperCase().endsWith("-DIGEST"); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java index af4dff23301..b04ac450154 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java @@ -217,6 +217,7 @@ public abstract class Packager { if (isLayered()) { writeLayerIndex(writer); } + writeSignatureFileIfNecessary(writtenLibraries, writer); } private void writeLoaderClasses(AbstractJarWriter writer) throws IOException { @@ -263,6 +264,10 @@ public abstract class Packager { } } + protected void writeSignatureFileIfNecessary(Map writtenLibraries, AbstractJarWriter writer) + throws IOException { + } + private EntryTransformer getEntityTransformer() { if (getLayout() instanceof RepackagingLayout repackagingLayout) { return new RepackagingEntryTransformer(repackagingLayout); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java index 07da873c83a..764c84f9fde 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package org.springframework.boot.loader.tools; import java.io.File; import java.io.IOException; import java.nio.file.attribute.FileTime; +import java.util.Map; import java.util.jar.JarFile; import org.springframework.util.Assert; @@ -46,6 +47,24 @@ public class Repackager extends Packager { super(source); } + @Override + protected void writeSignatureFileIfNecessary(Map writtenLibraries, AbstractJarWriter writer) + throws IOException { + if (getSource().getName().toLowerCase().endsWith(".jar") && hasSignedLibrary(writtenLibraries)) { + writer.writeEntry("META-INF/BOOT.SF", (entryWriter) -> { + }); + } + } + + private boolean hasSignedLibrary(Map writtenLibraries) throws IOException { + for (Library library : writtenLibraries.values()) { + if (!(library instanceof JarModeLibrary) && FileUtils.isSignedJarFile(library.getFile())) { + return true; + } + } + return false; + } + /** * Sets if source files should be backed up when they would be overwritten. * @param backupSource if source files should be backed up diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java index c4986aba568..1193ce35959 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java @@ -660,7 +660,7 @@ abstract class AbstractPackagerTests

{ return library.getFile(); } - private Library newLibrary(File file, LibraryScope scope, boolean unpackRequired) { + protected Library newLibrary(File file, LibraryScope scope, boolean unpackRequired) { return new Library(null, file, scope, null, unpackRequired, false, true); } @@ -687,7 +687,7 @@ abstract class AbstractPackagerTests

{ && hasPackagedEntry("org/springframework/boot/loader/launch/JarLauncher.class"); } - private boolean hasPackagedEntry(String name) throws IOException { + protected boolean hasPackagedEntry(String name) throws IOException { return getPackagedEntry(name) != null; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/FileUtilsTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/FileUtilsTests.java index edf8b38b889..e6e084bc4d6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/FileUtilsTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/FileUtilsTests.java @@ -17,9 +17,13 @@ package org.springframework.boot.loader.tools; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -99,4 +103,28 @@ class FileUtilsTests { assertThat(FileUtils.sha1Hash(file)).isEqualTo("7037807198c22a7d2b0807371d763779a84fdfcf"); } + @Test + void isSignedJarFileWhenSignedReturnsTrue() throws IOException { + Manifest manifest = new Manifest(getClass().getResourceAsStream("signed-manifest.mf")); + File jarFile = new File(this.tempDir, "test.jar"); + writeTestJar(manifest, jarFile); + assertThat(FileUtils.isSignedJarFile(jarFile)).isTrue(); + } + + @Test + void isSignedJarFileWhenNotSignedReturnsFalse() throws IOException { + Manifest manifest = new Manifest(); + File jarFile = new File(this.tempDir, "test.jar"); + writeTestJar(manifest, jarFile); + assertThat(FileUtils.isSignedJarFile(jarFile)).isFalse(); + } + + private void writeTestJar(Manifest manifest, File jarFile) throws IOException, FileNotFoundException { + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jarFile))) { + out.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + manifest.write(out); + out.closeEntry(); + } + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java index f1dd7d583d2..239c0cc381e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.List; +import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -218,6 +219,20 @@ class RepackagerTests extends AbstractPackagerTests { assertThat(stopWatch.getTotalTimeMillis()).isLessThan(5000); } + @Test + void signedJar() throws Exception { + Repackager packager = createPackager(); + packager.setMainClass("a.b.C"); + Manifest manifest = new Manifest(); + Attributes attributes = new Attributes(); + attributes.putValue("SHA1-Digest", "0000"); + manifest.getEntries().put("a/b/C.class", attributes); + TestJarFile libJar = new TestJarFile(this.tempDir); + libJar.addManifest(manifest); + execute(packager, (callback) -> callback.library(newLibrary(libJar.getFile(), LibraryScope.COMPILE, false))); + assertThat(hasPackagedEntry("META-INF/BOOT.SF")).isTrue(); + } + private boolean hasLauncherClasses(File file) throws IOException { return hasEntry(file, "org/springframework/boot/") && hasEntry(file, "org/springframework/boot/loader/launch/JarLauncher.class"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/resources/org/springframework/boot/loader/tools/signed-manifest.mf b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/resources/org/springframework/boot/loader/tools/signed-manifest.mf new file mode 100644 index 00000000000..8316a0550d5 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/resources/org/springframework/boot/loader/tools/signed-manifest.mf @@ -0,0 +1,9 @@ +Manifest-Version: 1.0 +Created-By: 1.5.0_08 (Sun Microsystems Inc.) +Specification-Version: 1.1 + +Name: org/bouncycastle/pqc/legacy/math/linearalgebra/GoppaCode.class +SHA-256-Digest: wNhEfeTvNG9ggqKfLjQDDoFoDqeWwGUc47JiL7VqxqU= + +Name: org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.class +SHA-256-Digest: nqljr9DNx4nNie4sbkZajVenvd3LdMF3X5s5dmSMToM= diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java index c903da23a2c..b89459cdf34 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java @@ -459,4 +459,12 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests { }); } + @TestTemplate + void whenSigned(MavenBuild mavenBuild) { + mavenBuild.project("jar-signed").execute((project) -> { + File repackaged = new File(project, "target/jar-signed-0.0.1.BUILD-SNAPSHOT.jar"); + assertThat(jar(repackaged)).hasEntryWithName("META-INF/BOOT.SF"); + }); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-signed/pom.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-signed/pom.xml new file mode 100644 index 00000000000..375d3c60b3d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-signed/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + org.springframework.boot.maven.it + jar-signed + 0.0.1.BUILD-SNAPSHOT + + UTF-8 + @java.version@ + @java.version@ + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + repackage + + + + + + org.apache.maven.plugins + maven-jar-plugin + @maven-jar-plugin.version@ + + + + some.random.Main + + + Foo + + + + + + + + + org.springframework + spring-context + @spring-framework.version@ + + + jakarta.servlet + jakarta.servlet-api + @jakarta-servlet.version@ + provided + + + org.bouncycastle + bcprov-jdk18on + 1.76 + + + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-signed/src/main/java/org/test/SampleApplication.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-signed/src/main/java/org/test/SampleApplication.java new file mode 100644 index 00000000000..5e51546d4e0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-signed/src/main/java/org/test/SampleApplication.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.test; + +public class SampleApplication { + + public static void main(String[] args) { + } + +} diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java b/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java index ac6592bc9d6..3151e334d9e 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java @@ -37,7 +37,6 @@ import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnava import org.springframework.util.Assert; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assumptions.assumeThat; /** * Integration tests loader that supports fat jars. @@ -66,7 +65,6 @@ class LoaderIntegrationTests { @ParameterizedTest @MethodSource("javaRuntimes") void runSignedJar(JavaRuntime javaRuntime) { - assumeThat(javaRuntime.toString()).isNotEqualTo("Oracle JDK 17"); // gh-28837 try (GenericContainer container = createContainer(javaRuntime, "spring-boot-loader-tests-signed-jar", null)) { container.start();