diff --git a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java index e43b78254b2..21d9acb90a2 100644 --- a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java +++ b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Eclipse Foundation and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024 Eclipse Foundation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -239,7 +239,26 @@ private static boolean useMethodHandles(final ClassLoader loader, final Class if (anchorClass == null || loader.getParent() == null || loader.getClass() == ASURLClassLoader.class) { return false; } - // Use MethodHandles.Lookup only if the anchor run-time Package defined by CL. - return Objects.equals(anchorClass.getPackage(), loader.getDefinedPackage(targetPackageName)); + // Use MethodHandles.Lookup only if the anchor class has the same package + // and anchor class is visible to the application classloader. + return Objects.equals(anchorClass.getPackageName(), targetPackageName) && isVisible(anchorClass, loader); + } + + /** + * Checks that the anchor class {@code anchorClass} is visible to the application + * classloader {@code loader}. + * + * @param anchorClass the anchor class + * @param loader the application classloader + */ + private static boolean isVisible(Class anchorClass, ClassLoader loader) { + ClassLoader anchorLoader = anchorClass.getClassLoader(); + while (loader != null) { + if (loader.equals(anchorLoader)) { + return true; + } + loader = loader.getParent(); + } + return false; } } diff --git a/appserver/itest-tools/src/main/java/org/glassfish/main/itest/tools/GlassFishTestEnvironment.java b/appserver/itest-tools/src/main/java/org/glassfish/main/itest/tools/GlassFishTestEnvironment.java index 207dce95d66..2b43bd07dcb 100644 --- a/appserver/itest-tools/src/main/java/org/glassfish/main/itest/tools/GlassFishTestEnvironment.java +++ b/appserver/itest-tools/src/main/java/org/glassfish/main/itest/tools/GlassFishTestEnvironment.java @@ -67,6 +67,7 @@ public class GlassFishTestEnvironment { private static final File ASADMIN = findAsadmin(); private static final File STARTSERV = findStartServ(); private static final File KEYTOOL = findKeyTool(); + private static final File JARSIGNER = findJarSigner(); private static final File PASSWORD_FILE_FOR_UPDATE = findPasswordFile("password_update.txt"); private static final File PASSWORD_FILE = findPasswordFile("password.txt"); @@ -130,6 +131,10 @@ public static KeyTool getKeyTool() { } + public static JarSigner getJarSigner() { + return new JarSigner(JARSIGNER); + } + /** * @return project's target directory. */ @@ -161,6 +166,7 @@ public static KeyStore getDomain1TrustStore() { public static int getPort(HttpListenerType listenerType) { return listenerType.getPort(); } + /** * Creates a {@link Client} instance for the domain administrator. * Caller is responsible for closing. @@ -320,6 +326,9 @@ private static File findKeyTool() { return new File(System.getProperty("java.home"), isWindows() ? "bin/keytool.exe" : "bin/keytool"); } + private static File findJarSigner() { + return new File(System.getProperty("java.home"), isWindows() ? "bin/jarsigner.exe" : "bin/jarsigner"); + } private static boolean isWindows() { return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win"); diff --git a/appserver/itest-tools/src/main/java/org/glassfish/main/itest/tools/JarSigner.java b/appserver/itest-tools/src/main/java/org/glassfish/main/itest/tools/JarSigner.java new file mode 100644 index 00000000000..63caf4452b2 --- /dev/null +++ b/appserver/itest-tools/src/main/java/org/glassfish/main/itest/tools/JarSigner.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.main.itest.tools; + +import com.sun.enterprise.universal.process.ProcessManager; +import com.sun.enterprise.universal.process.ProcessManagerException; +import com.sun.enterprise.universal.process.ProcessManagerTimeoutException; + +import java.io.File; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Tool for executing jarsigner/jarsigner.exe commands. + * The tool is stateless. + */ +public class JarSigner { + + private static final Logger LOG = System.getLogger(JarSigner.class.getName()); + + private final File jarsigner; + + public JarSigner(File jarsigner) { + this.jarsigner = jarsigner; + } + + /** + * Executes the command with arguments. + * + * @param args the arguments + */ + public void exec(String... args) { + final List parameters = Arrays.asList(args); + LOG.log(Level.INFO, "exec(args={0})", parameters); + final List command = new ArrayList<>(); + command.add(jarsigner.getAbsolutePath()); + command.addAll(parameters); + + final ProcessManager processManager = new ProcessManager(command); + processManager.setTimeoutMsec(60_000); + processManager.setEcho(true); + + int exitCode; + String errorMessage = ""; + try { + exitCode = processManager.execute(); + } catch (ProcessManagerTimeoutException e) { + errorMessage = e.getMessage(); + exitCode = 1; + } catch (ProcessManagerException e) { + LOG.log(Level.ERROR, "The execution failed.", e); + errorMessage = e.getMessage(); + exitCode = 1; + } + + final String stdErr = processManager.getStderr() + "\n" + errorMessage; + if (!processManager.getStdout().isEmpty()) { + System.out.println(processManager.getStdout()); + } + if (!processManager.getStderr().isEmpty()) { + System.err.println(processManager.getStderr()); + } + if (exitCode != 0) { + throw new RuntimeException(stdErr); + } + } +} diff --git a/appserver/tests/application/src/main/java/org/glassfish/main/test/app/signedear/api/ExampleRemote.java b/appserver/tests/application/src/main/java/org/glassfish/main/test/app/signedear/api/ExampleRemote.java new file mode 100644 index 00000000000..9d49f2c352a --- /dev/null +++ b/appserver/tests/application/src/main/java/org/glassfish/main/test/app/signedear/api/ExampleRemote.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.main.test.app.signedear.api; + +import jakarta.ejb.Remote; + +@Remote +public interface ExampleRemote { + + void example(); +} diff --git a/appserver/tests/application/src/main/java/org/glassfish/main/test/app/signedear/impl/ExampleBean.java b/appserver/tests/application/src/main/java/org/glassfish/main/test/app/signedear/impl/ExampleBean.java new file mode 100644 index 00000000000..abe06dff06c --- /dev/null +++ b/appserver/tests/application/src/main/java/org/glassfish/main/test/app/signedear/impl/ExampleBean.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.main.test.app.signedear.impl; + +import jakarta.ejb.Stateless; + +import org.glassfish.main.test.app.signedear.api.ExampleRemote; + +@Stateless +public class ExampleBean implements ExampleRemote { + + @Override + public void example() { + // do nothing + } +} diff --git a/appserver/tests/application/src/test/java/org/glassfish/main/test/app/signedear/SignedEarDeploymentTest.java b/appserver/tests/application/src/test/java/org/glassfish/main/test/app/signedear/SignedEarDeploymentTest.java new file mode 100644 index 00000000000..90ebb6e51bc --- /dev/null +++ b/appserver/tests/application/src/test/java/org/glassfish/main/test/app/signedear/SignedEarDeploymentTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.main.test.app.signedear; + +import java.io.File; +import java.lang.System.Logger.Level; +import java.util.UUID; + +import org.glassfish.main.itest.tools.GlassFishTestEnvironment; +import org.glassfish.main.itest.tools.JarSigner; +import org.glassfish.main.itest.tools.KeyTool; +import org.glassfish.main.itest.tools.asadmin.Asadmin; +import org.glassfish.main.test.app.signedear.api.ExampleRemote; +import org.glassfish.main.test.app.signedear.impl.ExampleBean; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.exporter.ZipExporter; +import org.jboss.shrinkwrap.api.spec.EnterpriseArchive; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.glassfish.main.itest.tools.asadmin.AsadminResultMatcher.asadminOK; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Integration test for deployment EAR application that contains signed + * shared libraries and modules. + * + *

This integration test checks the correctness of the classloading + * of generated classes. + */ +public class SignedEarDeploymentTest { + + private static final System.Logger LOG = System.getLogger(SignedEarDeploymentTest.class.getName()); + + private static final String APP_NAME = "signed"; + + private static final String KEYSTORE_PASSWORD = UUID.randomUUID().toString(); + + private static final Asadmin ASADMIN = GlassFishTestEnvironment.getAsadmin(); + private static final KeyTool KEYTOOL = GlassFishTestEnvironment.getKeyTool(); + private static final JarSigner JARSIGNER = GlassFishTestEnvironment.getJarSigner(); + + @TempDir + private static File tempDir; + private static File earFile; + + @BeforeAll + public static void prepareDeployment() { + File keyStore = new File(tempDir, "signtest.jks"); + + // Generate a key pair (a public key and associated private key). + KEYTOOL.exec("-genkeypair", "-alias", "signtest", "-keyalg", "RSA", "-dname", + "CN=SIGNTEST, OU=Eclipse Glassfish Tests, O=Eclipse Foundation, L=Brussels, ST=Belgium, C=Belgium", + "-validity", "7", "-keypass", KEYSTORE_PASSWORD, "-keystore", keyStore.getAbsolutePath(), + "-storepass", KEYSTORE_PASSWORD); + + // Create shared library. + JavaArchive apiArchive = ShrinkWrap.create(JavaArchive.class) + .addClass(ExampleRemote.class); + File apiFile = new File(tempDir, "api.jar"); + apiArchive.as(ZipExporter.class).exportTo(apiFile); + LOG.log(Level.INFO, apiArchive.toString(true)); + + // Sign shared library. + JARSIGNER.exec("-keystore", keyStore.getAbsolutePath(), "-storepass", KEYSTORE_PASSWORD, + "-keypass", KEYSTORE_PASSWORD, apiFile.getAbsolutePath(), "signtest"); + + // Create EAR EJB module. + JavaArchive implArchive = ShrinkWrap.create(JavaArchive.class) + .addClass(ExampleBean.class); + File implFile = new File(tempDir, "impl.jar"); + implArchive.as(ZipExporter.class).exportTo(implFile); + LOG.log(Level.INFO, implArchive.toString(true)); + + // Sign EAR EJB module. + JARSIGNER.exec("-keystore", keyStore.getAbsolutePath(), "-storepass", KEYSTORE_PASSWORD, + "-keypass", KEYSTORE_PASSWORD, implFile.getAbsolutePath(), "signtest"); + + // Create EAR application. + EnterpriseArchive enterpriseArchive = ShrinkWrap.create(EnterpriseArchive.class) + .addAsLibrary(apiFile) + .addAsModule(implFile); + earFile = new File(tempDir, APP_NAME + ".ear"); + enterpriseArchive.as(ZipExporter.class).exportTo(earFile); + LOG.log(Level.INFO, enterpriseArchive.toString(true)); + } + + @AfterAll + public static void cleanup() { + ASADMIN.exec("undeploy", APP_NAME); + } + + @Test + public void testDeployment() { + assertThat(ASADMIN.exec("deploy", earFile.getAbsolutePath()), asadminOK()); + } +}