1 /*
  2  * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 import java.nio.file.Files;
 25 import java.nio.file.Path;
 26 import java.nio.file.Paths;
 27 import java.nio.file.StandardCopyOption;
 28 import java.security.Security;
 29 import java.util.Collections;
 30 import java.util.HashMap;
 31 import java.util.LinkedList;
 32 import java.util.List;
 33 import java.util.Map;
 34 import java.util.Arrays;
 35 import java.util.stream.Stream;
 36 import java.io.File;
 37 import java.io.IOException;
 38 import java.io.OutputStream;
 39 import java.lang.module.ModuleDescriptor;
 40 import java.lang.module.ModuleDescriptor.Builder;
 41 import jdk.test.lib.process.ProcessTools;
 42 import jdk.test.lib.util.JarUtils;
 43 import jdk.test.lib.util.ModuleInfoWriter;
 44 
 45 /*
 46  * @test
 47  * @bug 8130360 8183310
 48  * @summary Test security provider in different combination of modular option
 49  *          defined with(out) service description.
 50  * @enablePreview
 51  * @modules java.base/jdk.internal.module
 52  * @library /test/lib
 53  * @build jdk.test.lib.util.JarUtils
 54  *        jdk.test.lib.util.ModuleInfoWriter
 55  *        TestProvider TestClient
 56  * @run main SecurityProviderModularTest CL true
 57  * @run main SecurityProviderModularTest CL false
 58  * @run main SecurityProviderModularTest SL true
 59  * @run main SecurityProviderModularTest SL false
 60  * @run main SecurityProviderModularTest SPN true
 61  * @run main SecurityProviderModularTest SPN false
 62  * @run main SecurityProviderModularTest SPT true
 63  * @run main SecurityProviderModularTest SPT false
 64  */
 65 public class SecurityProviderModularTest {
 66 
 67     private static final Path TEST_CLASSES
 68             = Paths.get(System.getProperty("test.classes"));
 69     private static final Path ARTIFACT_DIR = Paths.get("jars");
 70     private static final Path SEC_FILE = Paths.get("java.extn.security");
 71     private static final String PS = File.pathSeparator;
 72     private static final String P_TYPE = "p.TestProvider";
 73     private static final String C_TYPE = "c.TestClient";
 74 
 75     /**
 76      * Here is the naming convention followed.
 77      * Test runtime arguments,
 78      * CL       - Provider class loaded through ClassLoader
 79      * SL       - Provider class to be discovered by ServiceLoader
 80      * SPN      - Provider name defined through "java.extn.security" file which
 81      *            referred through system property "java.security.properties".
 82      * SPT      - Provider type defined through "java.extn.security" file which
 83      *            referred through system property "java.security.properties".
 84      *
 85      * For each jar file name,
 86      * p.jar    - Unnamed provider jar.
 87      * pd.jar   - Unnamed provider jar with META-INF provider descriptor.
 88      * mp.jar   - Modular provider jar.
 89      * mpd.jar  - Modular provider jar with META-INF provider descriptor.
 90      * msp.jar  - Modular provider jar provides service through module-info.java
 91      * mspd.jar - Modular provider jar with META-INF provider descriptor and
 92      *            provides service through module-info.java.
 93      * c.jar    - Unnamed client jar.
 94      * mc.jar   - Modular client jar.
 95      * mcs.jar  - Modular client jar uses service through module-info.java.
 96      * amc.jar  - Modular client used for automatic provider jar.
 97      * amcs.jar - Modular client used for automatic provider jar uses service
 98      *            through module-info.java.
 99      */
100     private static final Path P_JAR = artifact("p.jar");
101     private static final Path PD_JAR = artifact("pd.jar");
102     private static final Path MP_JAR = artifact("mp.jar");
103     private static final Path MPD_JAR = artifact("mpd.jar");
104     private static final Path MSP_JAR = artifact("msp.jar");
105     private static final Path MSPD_JAR = artifact("mspd.jar");
106     private static final Path C_JAR = artifact("c.jar");
107     private static final Path MC_JAR = artifact("mc.jar");
108     private static final Path MCS_JAR = artifact("mcs.jar");
109     private static final Path AMC_JAR = artifact("amc.jar");
110     private static final Path AMCS_JAR = artifact("amcs.jar");
111     private static final Map<String, String> MSG_MAP = new HashMap<>();
112 
113     static {
114         /*
115          * This mapping help process finding expected message based
116          * on the key passed as argument while executing java command.
117          */
118         MSG_MAP.put("NoAccess", "cannot access class p.TestProvider");
119         MSG_MAP.put("Success", "Client: found provider TestProvider");
120         MSG_MAP.put("NoProvider", "Provider TestProvider not found");
121     }
122 
123     private final String addUNArg;
124     private final String addNMArg;
125     private final String cArg;
126     private final String unnP;
127     private final String modP;
128     private final String unnC;
129     private final String modC;
130     private final String autoMC;
131     private final String expModRes;
132     private final String expAModRes;
133     // Common set of VM arguments used in all test cases
134     private final List<String> commonArgs;
135 
136     public SecurityProviderModularTest(String use, boolean metaDesc) {
137 
138         List<String> argList = new LinkedList<>();
139         argList.add("-Duser.language=en");
140         argList.add("-Duser.region=US");
141         final boolean useSL = "SL".equals(use) || "SPN".equals(use);
142         final boolean useCL = "CL".equals(use);
143         final boolean useSPT = "SPT".equals(use);
144         final boolean useSP = use.startsWith("SP");
145         /* Use Security property file when the provider expected to
146          * loaded through Security property file. */
147         if (useSP) {
148             /* Create a java.security file to specify the new provider.
149              * java.security file extension can be provided using
150              * "-Djava.security.properties" VM argument at runtime.*/
151             createJavaSecurityFileExtn("SPN".equals(use));
152             argList.add("-Djava.security.properties=" + toAbsPath(SEC_FILE));
153         }
154         commonArgs = Collections.unmodifiableList(argList);
155         cArg = (useCL) ? P_TYPE : "TestProvider";
156         addUNArg = (useSL) ? "" : ("--add-modules="
157                 + ((metaDesc) ? "pd" : "p"));
158         addNMArg = (useSL) ? "" : "--add-modules=mp";
159 
160         // Based on Testcase, select unnamed/modular jar files to use.
161         unnP = toAbsPath((metaDesc) ? PD_JAR : P_JAR);
162         modP = toAbsPath(useSL ? (metaDesc ? MSPD_JAR : MSP_JAR)
163                 : (metaDesc ? MPD_JAR : MP_JAR));
164         unnC = toAbsPath(C_JAR);
165         modC = toAbsPath(useSL ? MCS_JAR : MC_JAR);
166         autoMC = toAbsPath(useSL ? AMCS_JAR : AMC_JAR);
167 
168         expModRes = "Success";
169         expAModRes = (useSPT | useCL) ? "Success"
170                 : (metaDesc) ? "Success" : "NoProvider";
171         String loadByMsg = useSP ? "SecurityPropertyFile"
172                 : ((useCL) ? "ClassLoader" : "ServiceLoader");
173         System.out.printf("%n*** Providers loaded through %s and includes"
174                 + " META Descriptor: %s ***%n%n", loadByMsg, metaDesc);
175     }
176 
177     /*
178      * Test cases are based on the following logic,
179      * for (ProviderLoadedThrough : {"ServiceLoader", "ClassLoader",
180      *             "SecurityPropertyFile"}) {
181      *     for (definedWith : {"METAINFService", "WithoutMETAINFService"}) {
182      *         for (clientType : {"NAMED", "AUTOMATIC", "UNNAMED"}) {
183      *             for (providerType : {"NAMED", "AUTOMATIC", "UNNAMED"}) {
184      *                 Create and run java command for each possible case
185      *             }
186      *         }
187      *     }
188      * }
189      */
190     public static void main(String[] args) throws Exception {
191 
192         // Generates unnamed and modular jars.
193         setUp();
194         boolean metaDesc = Boolean.valueOf(args[1]);
195         SecurityProviderModularTest test
196                 = new SecurityProviderModularTest(args[0], metaDesc);
197         test.process(args[0]);
198     }
199 
200     private void process(String use) throws Exception {
201 
202         // Case: NAMED-NAMED, NAMED-AUTOMATIC, NAMED-UNNAMED
203         System.out.printf("Case: Modular Client and Modular Provider");
204         execute(String.format("--module-path %s%s%s -m mc/%s %s %s",
205                 modC, PS, modP, C_TYPE, use, cArg), expModRes);
206         System.out.printf("Case: Modular Client and automatic Provider");
207         execute(String.format("--module-path %s%s%s %s -m mc/%s %s %s", autoMC,
208                 PS, unnP, addUNArg, C_TYPE, use, cArg), expAModRes);
209         System.out.printf("Case: Modular Client and unnamed Provider");
210         execute(String.format("--module-path %s -cp %s -m mc/%s %s %s", autoMC,
211                 unnP, C_TYPE, use, cArg), expAModRes);
212 
213         // Case: AUTOMATIC-NAMED, AUTOMATIC-AUTOMATIC, AUTOMATIC-UNNAMED
214         System.out.printf("Case: Automatic Client and modular Provider");
215         execute(String.format("--module-path %s%s%s %s -m c/%s %s %s", unnC,
216                 PS, modP, addNMArg, C_TYPE, use, cArg), expModRes);
217         System.out.printf("Case: Automatic Client and automatic Provider");
218         execute(String.format("--module-path %s%s%s %s -m c/%s %s %s", unnC,
219                 PS, unnP, addUNArg, C_TYPE, use, cArg), expAModRes);
220         System.out.printf("Case: Automatic Client and unnamed Provider");
221         execute(String.format("--module-path %s -cp %s -m c/%s %s %s", unnC,
222                 unnP, C_TYPE, use, cArg), expAModRes);
223 
224         // Case: UNNAMED-NAMED, UNNAMED-AUTOMATIC, UNNAMED-UNNAMED
225         System.out.printf("Case: Unnamed Client and modular Provider");
226         execute(String.format("-cp %s --module-path %s %s %s %s %s", unnC,
227                 modP, addNMArg, C_TYPE, use, cArg), expModRes);
228         System.out.printf("Case: Unnamed Client and automatic Provider");
229         execute(String.format("-cp %s --module-path %s %s %s %s %s", unnC,
230                 unnP, addUNArg, C_TYPE, use, cArg), expAModRes);
231         System.out.printf("Case: Unnamed Client and unnamed Provider");
232         execute(String.format("-cp %s%s%s %s %s %s", unnC, PS, unnP, C_TYPE,
233                 use, cArg), expAModRes);
234 
235         // Case: unnamed jars in --module-path and modular jars in -cp.
236         System.out.printf(
237                 "Case: Unnamed Client and Unnamed Provider in modulepath");
238         execute(String.format("--module-path %s%s%s %s -m c/%s %s %s", unnC,
239                 PS, unnP, addUNArg, C_TYPE, use, cArg), expAModRes);
240         System.out.printf(
241                 "Case: Modular Client and Modular Provider in classpath");
242         execute(String.format("-cp %s%s%s %s %s %s", modC, PS, modP, C_TYPE,
243                 use, cArg), expAModRes);
244     }
245 
246     /**
247      * Execute with command arguments and process the result.
248      */
249     private void execute(String args, String msgKey) throws Exception {
250 
251         String[] safeArgs = Stream.concat(commonArgs.stream(),
252                 Stream.of(args.split("\\s+"))).filter(s -> {
253             if (s.contains(" ")) {
254                 throw new RuntimeException("No spaces in args");
255             }
256             return !s.isEmpty();
257         }).toArray(String[]::new);
258         String out = ProcessTools.executeTestJava(safeArgs).getOutput();
259         // Handle response.
260         if ((msgKey != null && out.contains(MSG_MAP.get(msgKey)))) {
261             System.out.printf("PASS: Expected Result: %s.%n",
262                     MSG_MAP.get(msgKey));
263         } else if (out.contains("Exception") || out.contains("Error")) {
264             System.out.printf("OUTPUT: %s", out);
265             throw new RuntimeException("FAIL: Unknown Exception occured. "
266                     + "Expected: " + MSG_MAP.get(msgKey));
267         } else {
268             System.out.printf("OUTPUT: %s", out);
269             throw new RuntimeException("FAIL: Unknown Test case found");
270         }
271     }
272 
273     /**
274      * Creates Unnamed/modular jar files for TestClient and TestClassLoader.
275      */
276     private static void setUp() throws Exception {
277 
278         if (ARTIFACT_DIR.toFile().exists()) {
279             System.out.println("Skipping setup: Artifacts already exists.");
280             return;
281         }
282         // Generate unnamed provider jar file.
283         JarUtils.createJarFile(P_JAR, TEST_CLASSES, "p/TestProvider.class");
284         // Generate unnamed client jar file.
285         JarUtils.createJarFile(C_JAR, TEST_CLASSES, "c/TestClient.class");
286         // Generate unnamed provider jar files with META-INF descriptor.
287         generateJar(P_JAR, PD_JAR, null, true);
288 
289         Builder mBuilder = ModuleDescriptor.newModule("mp").exports("p");
290         // Modular provider defined as META-INF service.
291         generateJar(P_JAR, MPD_JAR, mBuilder.build(), true);
292         // Modular jar exports package to let the provider type accessible.
293         generateJar(P_JAR, MP_JAR, mBuilder.build(), false);
294 
295         mBuilder = ModuleDescriptor.newModule("mp")
296                 .provides("java.security.Provider", Arrays.asList(P_TYPE));
297         // Modular provider Service in module-info does not need to export
298         // its package.
299         generateJar(P_JAR, MSP_JAR, mBuilder.build(), false);
300         // Modular provider Service in module-info also have META-INF descriptor
301         generateJar(P_JAR, MSPD_JAR, mBuilder.build(), true);
302 
303         mBuilder = ModuleDescriptor.newModule("mc").exports("c");
304         // Generate modular client jar file to use automatic provider jar.
305         generateJar(C_JAR, AMC_JAR, mBuilder.build(), false);
306         // Generate modular client jar file to use modular provider jar.
307         generateJar(C_JAR, MC_JAR, mBuilder.requires("mp").build(), false);
308 
309         mBuilder = ModuleDescriptor.newModule("mc").exports("c")
310                 .uses("java.security.Provider");
311         // Generate modular client jar file to use automatic provider service.
312         generateJar(C_JAR, AMCS_JAR, mBuilder.build(), false);
313         // Generate modular client jar file using modular provider service.
314         generateJar(C_JAR, MCS_JAR, mBuilder.requires("mp").build(), false);
315     }
316 
317     /**
318      * Update Unnamed jars and include descriptor files.
319      */
320     private static void generateJar(Path sjar, Path djar,
321             ModuleDescriptor mDesc, boolean metaDesc) throws Exception {
322 
323         Files.copy(sjar, djar, StandardCopyOption.REPLACE_EXISTING);
324         Path dir = Files.createTempDirectory("tmp");
325         if (metaDesc) {
326             write(dir.resolve(Paths.get("META-INF", "services",
327                     "java.security.Provider")), P_TYPE);
328         }
329         if (mDesc != null) {
330             Path mi = dir.resolve("module-info.class");
331             try (OutputStream out = Files.newOutputStream(mi)) {
332                 ModuleInfoWriter.write(mDesc, out);
333             }
334             System.out.format("Added 'module-info.class' in '%s'%n", djar);
335         }
336         JarUtils.updateJarFile(djar, dir);
337     }
338 
339     /**
340      * Look for file path in generated jars.
341      */
342     private static Path artifact(String file) {
343         return ARTIFACT_DIR.resolve(file);
344     }
345 
346     /**
347      * Convert to absolute file path.
348      */
349     private static String toAbsPath(Path path) {
350         return path.toFile().getAbsolutePath();
351     }
352 
353     /**
354      * Create the parent directories if missing to ensure the path exist.
355      */
356     private static Path ensurePath(Path at) throws IOException {
357         Path parent = at.getParent();
358         if (parent != null && !parent.toFile().exists()) {
359             ensurePath(parent);
360         }
361         return Files.createDirectories(parent);
362     }
363 
364     /**
365      * Generates service descriptor inside META-INF folder.
366      */
367     private static void write(Path at, String content) throws IOException {
368         ensurePath(at);
369         Files.write(at, content.getBytes("UTF-8"));
370     }
371 
372     /**
373      * Create new provider entry through java.security file extension.
374      * New provider entry will be the last entry inside the JRE.
375      */
376     private static void createJavaSecurityFileExtn(boolean useName) {
377         int insertAt = Security.getProviders().length + 1;
378         String provider = (useName ? "TestProvider" : P_TYPE);
379         try {
380             Files.write(SEC_FILE, String.format("security.provider.%s=%s",
381                     insertAt, provider).getBytes("UTF-8"));
382         } catch (IOException e) {
383             throw new RuntimeException(e);
384         }
385         System.out.printf("Security property file created at: %s with value:"
386                 + " %s%n", SEC_FILE, provider);
387     }
388 }