diff --git a/src/java.base/share/classes/java/security/Security.java b/src/java.base/share/classes/java/security/Security.java index 0cdd22340df..6628b717eb0 100644 --- a/src/java.base/share/classes/java/security/Security.java +++ b/src/java.base/share/classes/java/security/Security.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2024, 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 @@ -25,22 +25,39 @@ package java.security; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.io.*; +import java.net.URI; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import jdk.internal.access.JavaSecurityPropertiesAccess; +import jdk.internal.access.SharedSecrets; import jdk.internal.event.EventHelper; import jdk.internal.event.SecurityPropertyModificationEvent; -import jdk.internal.access.SharedSecrets; import jdk.internal.util.StaticProperty; +import sun.security.jca.GetInstance; +import sun.security.jca.ProviderList; +import sun.security.jca.Providers; import sun.security.util.Debug; import sun.security.util.PropertyExpander; -import sun.security.jca.*; - /** *

This class centralizes all security properties and common security * methods. One of its primary uses is to manage providers. @@ -63,7 +80,17 @@ public final class Security { Debug.getInstance("properties"); /* The java.security properties */ - private static Properties props; + private static final Properties props = new Properties() { + @Override + public synchronized Object put(Object key, Object val) { + if (key instanceof String strKey && val instanceof String strVal && + SecPropLoader.isInclude(strKey)) { + SecPropLoader.loadInclude(strVal); + return null; + } + return super.put(key, val); + } + }; /* cache a copy for recording purposes */ private static Properties initialSecurityProperties; @@ -74,11 +101,220 @@ public final class Security { Provider provider; } + private static final class SecPropLoader { + private enum LoadingMode {OVERRIDE, APPEND} + + private static final String OVERRIDE_SEC_PROP = + "security.overridePropertiesFile"; + + private static final String EXTRA_SYS_PROP = + "java.security.properties"; + + private static Path currentPath; + + private static final Set activePaths = new HashSet<>(); + + static void loadAll() { + // first load the master properties file to + // determine the value of OVERRIDE_SEC_PROP + loadMaster(); + loadExtra(); + } + + static boolean isInclude(String key) { + return "include".equals(key); + } + + static void checkReservedKey(String key) + throws IllegalArgumentException { + if (isInclude(key)) { + throw new IllegalArgumentException("Key '" + key + + "' is reserved and cannot be used as a " + + "Security property name."); + } + } + + private static void loadMaster() { + try { + loadFromPath(Path.of(StaticProperty.javaHome(), "conf", + "security", "java.security"), LoadingMode.APPEND); + } catch (IOException e) { + throw new InternalError("Error loading java.security file", e); + } + } + + private static void loadExtra() { + if ("true".equalsIgnoreCase(props.getProperty(OVERRIDE_SEC_PROP))) { + String propFile = System.getProperty(EXTRA_SYS_PROP); + if (propFile != null) { + LoadingMode mode = LoadingMode.APPEND; + if (propFile.startsWith("=")) { + mode = LoadingMode.OVERRIDE; + propFile = propFile.substring(1); + } + try { + loadExtraHelper(propFile, mode); + } catch (Exception e) { + if (sdebug != null) { + sdebug.println("unable to load security " + + "properties from " + propFile); + e.printStackTrace(); + } + } + } + } + } + + private static void loadExtraHelper(String propFile, LoadingMode mode) + throws Exception { + propFile = PropertyExpander.expand(propFile); + if (propFile.isEmpty()) { + throw new IOException("Empty extra properties file path"); + } + + // Try to interpret propFile as a path + Exception error; + if ((error = loadExtraFromPath(propFile, mode)) == null) { + return; + } + + // Try to interpret propFile as a file URL + URI uri = null; + try { + uri = new URI(propFile); + } catch (Exception ignore) {} + if (uri != null && "file".equalsIgnoreCase(uri.getScheme()) && + (error = loadExtraFromFileUrl(uri, mode)) == null) { + return; + } + + // Try to interpret propFile as a URL + URL url; + try { + url = newURL(propFile); + } catch (MalformedURLException ignore) { + // URL has no scheme: previous error is more accurate + throw error; + } + loadFromUrl(url, mode); + } + + private static Exception loadExtraFromPath(String propFile, + LoadingMode mode) throws Exception { + Path path; + try { + path = Path.of(propFile); + if (!Files.exists(path)) { + return new FileNotFoundException(propFile); + } + } catch (InvalidPathException e) { + return e; + } + loadFromPath(path, mode); + return null; + } + + + private static Exception loadExtraFromFileUrl(URI uri, LoadingMode mode) + throws Exception { + Path path; + try { + path = Path.of(uri); + } catch (Exception e) { + return e; + } + loadFromPath(path, mode); + return null; + } + + private static void reset(LoadingMode mode) { + if (mode == LoadingMode.OVERRIDE) { + if (sdebug != null) { + sdebug.println( + "overriding other security properties files!"); + } + props.clear(); + } + } + + static void loadInclude(String propFile) { + String expPropFile = PropertyExpander.expandNonStrict(propFile); + if (sdebug != null) { + sdebug.println("processing include: '" + propFile + "'" + + (propFile.equals(expPropFile) ? "" : + " (expanded to '" + expPropFile + "')")); + } + try { + Path path = Path.of(expPropFile); + if (!path.isAbsolute()) { + if (currentPath == null) { + throw new InternalError("Cannot resolve '" + + expPropFile + "' relative path when included " + + "from a non-regular properties file " + + "(e.g. HTTP served file)"); + } + path = currentPath.resolveSibling(path); + } + loadFromPath(path, LoadingMode.APPEND); + } catch (IOException | InvalidPathException e) { + throw new InternalError("Unable to include '" + expPropFile + + "'", e); + } + } + + private static void loadFromPath(Path path, LoadingMode mode) + throws IOException { + boolean isRegularFile = Files.isRegularFile(path); + if (isRegularFile) { + path = path.toRealPath(); + } else if (Files.isDirectory(path)) { + throw new IOException("Is a directory"); + } else { + path = path.toAbsolutePath(); + } + if (activePaths.contains(path)) { + throw new InternalError("Cyclic include of '" + path + "'"); + } + try (InputStream is = Files.newInputStream(path)) { + reset(mode); + Path previousPath = currentPath; + currentPath = isRegularFile ? path : null; + activePaths.add(path); + try { + debugLoad(true, path); + props.load(is); + debugLoad(false, path); + } finally { + activePaths.remove(path); + currentPath = previousPath; + } + } + } + + private static void loadFromUrl(URL url, LoadingMode mode) + throws IOException { + try (InputStream is = url.openStream()) { + reset(mode); + debugLoad(true, url); + props.load(is); + debugLoad(false, url); + } + } + + private static void debugLoad(boolean start, Object source) { + if (sdebug != null) { + int level = activePaths.isEmpty() ? 1 : activePaths.size(); + sdebug.println((start ? + ">".repeat(level) + " starting to process " : + "<".repeat(level) + " finished processing ") + source); + } + } + } + static { // doPrivileged here because there are multiple // things in initialize that might require privs. - // (the FileInputStream call and the File.exists call, - // the securityPropFile call, etc) + // (the FileInputStream call and the File.exists call, etc) @SuppressWarnings("removal") var dummy = AccessController.doPrivileged((PrivilegedAction) () -> { initialize(); @@ -94,28 +330,7 @@ public final class Security { } private static void initialize() { - props = new Properties(); - boolean overrideAll = false; - - // first load the system properties file - // to determine the value of security.overridePropertiesFile - File propFile = securityPropFile("java.security"); - boolean success = loadProps(propFile, null, false); - if (!success) { - throw new InternalError("Error loading java.security file"); - } - - if ("true".equalsIgnoreCase(props.getProperty - ("security.overridePropertiesFile"))) { - - String extraPropFile = System.getProperty - ("java.security.properties"); - if (extraPropFile != null && extraPropFile.startsWith("=")) { - overrideAll = true; - extraPropFile = extraPropFile.substring(1); - } - loadProps(null, extraPropFile, overrideAll); - } + SecPropLoader.loadAll(); initialSecurityProperties = (Properties) props.clone(); if (sdebug != null) { for (String key : props.stringPropertyNames()) { @@ -123,63 +338,6 @@ public final class Security { props.getProperty(key)); } } - - } - - private static boolean loadProps(File masterFile, String extraPropFile, boolean overrideAll) { - InputStream is = null; - try { - if (masterFile != null && masterFile.exists()) { - is = new FileInputStream(masterFile); - } else if (extraPropFile != null) { - extraPropFile = PropertyExpander.expand(extraPropFile); - File propFile = new File(extraPropFile); - URL propURL; - if (propFile.exists()) { - propURL = newURL - ("file:" + propFile.getCanonicalPath()); - } else { - propURL = newURL(extraPropFile); - } - - is = propURL.openStream(); - if (overrideAll) { - props = new Properties(); - if (sdebug != null) { - sdebug.println - ("overriding other security properties files!"); - } - } - } else { - // unexpected - return false; - } - props.load(is); - if (sdebug != null) { - // ExceptionInInitializerError if masterFile.getName() is - // called here (NPE!). Leave as is (and few lines down) - sdebug.println("reading security properties file: " + - masterFile == null ? extraPropFile : "java.security"); - } - return true; - } catch (IOException | PropertyExpander.ExpandException e) { - if (sdebug != null) { - sdebug.println("unable to load security properties from " + - masterFile == null ? extraPropFile : "java.security"); - e.printStackTrace(); - } - return false; - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException ioe) { - if (sdebug != null) { - sdebug.println("unable to close input stream"); - } - } - } - } } /** @@ -188,14 +346,6 @@ public final class Security { private Security() { } - private static File securityPropFile(String filename) { - // maybe check for a system property which will specify where to - // look. Someday. - String sep = File.separator; - return new File(StaticProperty.javaHome() + sep + "conf" + sep + - "security" + sep + filename); - } - /** * Looks up providers, and returns the property (and its associated * provider) mapping the key, if any. @@ -714,17 +864,16 @@ public final class Security { * denies * access to retrieve the specified security property value * @throws NullPointerException if key is {@code null} + * @throws IllegalArgumentException if key is reserved and cannot be + * used as a Security property name. Reserved keys are: + * "include". * * @see #setProperty * @see java.security.SecurityPermission */ public static String getProperty(String key) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(new SecurityPermission("getProperty."+ - key)); - } + SecPropLoader.checkReservedKey(key); + check("getProperty." + key); String name = props.getProperty(key); if (name != null) name = name.trim(); // could be a class name with trailing ws @@ -749,11 +898,15 @@ public final class Security { * java.lang.SecurityManager#checkPermission} method * denies access to set the specified security property value * @throws NullPointerException if key or datum is {@code null} + * @throws IllegalArgumentException if key is reserved and cannot be + * used as a Security property name. Reserved keys are: + * "include". * * @see #getProperty * @see java.security.SecurityPermission */ public static void setProperty(String key, String datum) { + SecPropLoader.checkReservedKey(key); check("setProperty." + key); props.put(key, datum); invalidateSMCache(key); /* See below. */ diff --git a/src/java.base/share/classes/sun/security/util/PropertyExpander.java b/src/java.base/share/classes/sun/security/util/PropertyExpander.java index 2cc4929e04b..e92d57bfee2 100644 --- a/src/java.base/share/classes/sun/security/util/PropertyExpander.java +++ b/src/java.base/share/classes/sun/security/util/PropertyExpander.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024, 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 @@ -28,6 +28,7 @@ package sun.security.util; import java.net.URI; import java.net.URISyntaxException; import java.security.GeneralSecurityException; +import java.util.function.UnaryOperator; /** * A utility class to expand properties embedded in a string. @@ -51,15 +52,31 @@ public class PropertyExpander { } } - public static String expand(String value) - throws ExpandException - { + public static String expand(String value) throws ExpandException { return expand(value, false); } - public static String expand(String value, boolean encodeURL) - throws ExpandException - { + public static String expand(String value, boolean encodeURL) + throws ExpandException { + return expand(value, encodeURL, System::getProperty); + } + + /* + * In non-strict mode an undefined property is replaced by an empty string. + */ + public static String expandNonStrict(String value) { + try { + return expand(value, false, key -> System.getProperty(key, "")); + } catch (ExpandException e) { + // should not happen + throw new AssertionError("unexpected expansion error: when " + + "expansion is non-strict, undefined properties should " + + "be replaced by an empty string", e); + } + } + + private static String expand(String value, boolean encodeURL, + UnaryOperator propertiesGetter) throws ExpandException { if (value == null) return null; @@ -105,7 +122,7 @@ public class PropertyExpander { if (prop.equals("/")) { sb.append(java.io.File.separatorChar); } else { - String val = System.getProperty(prop); + String val = propertiesGetter.apply(prop); if (val != null) { if (encodeURL) { // encode 'val' unless it's an absolute URI diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index c537a30960e..9651ae2d373 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -28,6 +28,33 @@ # Properties in this file are typically parsed only once. If any of the # properties are modified, applications should be restarted to ensure the # changes are properly reflected. +# +# The special "include" property can be defined one or multiple times with +# a filesystem path value. The effect of each definition is to include a +# referred security properties file inline, adding all its properties. +# Security properties defined before an include statement may be overridden +# by properties in the included file, if their names match. Conversely, +# properties defined after an include statement may override properties in +# the included file. +# +# Included files, as well as files pointed to by java.security.properties, +# can include other files recursively. Paths may be absolute or relative. +# Each relative path is resolved against the base file containing its +# "include" definition, if local. Paths may contain system properties for +# expansion in the form of ${system.property}. If a system property does +# not have a value, it expands to the empty string. +# +# An error will be thrown if a file cannot be included. This may happen +# if the file cannot be resolved, does not exist, is a directory, there are +# insufficient permissions to read it, it is recursively included more than +# once, or for any other reason. For a secure JDK configuration, it is +# important to review OS write permissions assigned to any file included. +# +# Examples: +# 1) include ${java.home}/conf/security/extra.security +# 2) include extra.security +# 3) include ${java.home}/conf/security/profile${SecurityProfile}.security +# # In this file, various security properties are set for use by # java.security classes. This is where users can statically register diff --git a/test/jdk/java/security/Security/ConfigFileTest.java b/test/jdk/java/security/Security/ConfigFileTest.java index b9264b937ec..caf657005e1 100644 --- a/test/jdk/java/security/Security/ConfigFileTest.java +++ b/test/jdk/java/security/Security/ConfigFileTest.java @@ -21,155 +21,907 @@ * questions. */ +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpServer; import jdk.test.lib.Utils; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.util.FileUtils; +import sun.net.www.ParseUtil; +import java.io.Closeable; import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; import java.io.UncheckedIOException; -import java.nio.file.*; - -import java.security.Provider; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.CharBuffer; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.security.Security; +import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.Optional; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; /* * @test - * @summary Throw error if default java.security file is missing - * @bug 8155246 8292297 8292177 8281658 + * @summary Tests security properties passed through java.security, + * java.security.properties or included from other properties files. + * @bug 8155246 8292297 8292177 8281658 8319332 + * @modules java.base/sun.net.www * @library /test/lib * @run main ConfigFileTest */ + public class ConfigFileTest { + static final String SEPARATOR_THIN = "----------------------------"; - private static final String EXPECTED_DEBUG_OUTPUT = - "Initial security property: crypto.policy=unlimited"; - - private static final String UNEXPECTED_DEBUG_OUTPUT = - "Initial security property: postInitTest=shouldNotRecord"; - - private static boolean overrideDetected = false; - - private static Path COPY_JDK_DIR = Path.of("./jdk-8155246-tmpdir"); - private static Path COPIED_JAVA = COPY_JDK_DIR.resolve("bin", "java"); + private static void printTestHeader(String testName) { + System.out.println(); + System.out.println(SEPARATOR_THIN); + System.out.println(testName); + System.out.println(SEPARATOR_THIN); + System.out.println(); + } public static void main(String[] args) throws Exception { - Path copyJdkDir = Path.of("./jdk-8155246-tmpdir"); - Path copiedJava = Optional.of( - Path.of(copyJdkDir.toString(), "bin", "java")) - .orElseThrow(() -> new RuntimeException("Unable to locate new JDK") - ); - - if (args.length == 1) { - // set up is complete. Run code to exercise loading of java.security - Provider[] provs = Security.getProviders(); + if (args.length == 1 && Executor.RUNNER_ARG.equals(args[0])) { + // Executed by a test-launched JVM. + // Force the initialization of java.security.Security. + Security.getProviders(); Security.setProperty("postInitTest", "shouldNotRecord"); - System.out.println(Arrays.toString(provs) + "NumProviders: " + provs.length); + System.out.println(FilesManager.LAST_FILE_PROP_NAME + ": " + + Security.getProperty(FilesManager.LAST_FILE_PROP_NAME)); + assertTestSecuritySetPropertyShouldNotInclude(); } else { - Files.createDirectory(copyJdkDir); - Path jdkTestDir = Path.of(Optional.of(System.getProperty("test.jdk")) - .orElseThrow(() -> new RuntimeException("Couldn't load JDK Test Dir")) - ); - - copyJDK(jdkTestDir, copyJdkDir); - String extraPropsFile = Path.of(System.getProperty("test.src"), "override.props").toString(); - - // sanity test -XshowSettings:security option - exerciseShowSettingsSecurity(buildCommand("-cp", System.getProperty("test.classes"), - "-Djava.security.debug=all", "-XshowSettings:security", "ConfigFileTest", "runner")); - - // exercise some debug flags while we're here - // regular JDK install - should expect success - exerciseSecurity(0, "java", - buildCommand("-cp", System.getProperty("test.classes"), - "-Djava.security.debug=all", "-Djavax.net.debug=all", "ConfigFileTest", "runner")); - - // given an overriding security conf file that doesn't exist, we shouldn't - // overwrite the properties from original/master security conf file - exerciseSecurity(0, "SUN version", - buildCommand("-cp", System.getProperty("test.classes"), - "-Djava.security.debug=all", "-Djavax.net.debug=all", - "-Djava.security.properties==file:///" + extraPropsFile + "badFileName", - "ConfigFileTest", "runner")); - - // test JDK launch with customized properties file - exerciseSecurity(0, "NumProviders: 6", - buildCommand("-cp", System.getProperty("test.classes"), - "-Djava.security.debug=all", "-Djavax.net.debug=all", - "-Djava.security.properties==file:///" + extraPropsFile, - "ConfigFileTest", "runner")); - - // delete the master conf file - Files.delete(Path.of(copyJdkDir.toString(), "conf", - "security","java.security")); - - // launch JDK without java.security file being present or specified - exerciseSecurity(1, "Error loading java.security file", - buildCommand("-cp", System.getProperty("test.classes"), - "-Djava.security.debug=all", "-Djavax.net.debug=all", - "ConfigFileTest", "runner")); - - // test the override functionality also. Should not be allowed since - // "security.overridePropertiesFile=true" Security property is missing. - exerciseSecurity(1, "Error loading java.security file", - buildCommand("-cp", System.getProperty("test.classes"), - "-Djava.security.debug=all", "-Djavax.net.debug=all", - "-Djava.security.properties==file:///" + extraPropsFile, "ConfigFileTest", "runner")); - - if (!overrideDetected) { - throw new RuntimeException("Override scenario not seen"); + // Executed by the test JVM. + try (FilesManager filesMgr = new FilesManager()) { + for (Method m : ConfigFileTest.class.getDeclaredMethods()) { + if (m.getName().startsWith("test")) { + printTestHeader(m.getName()); + Executor.run(m, filesMgr); + } + } } } } - private static ProcessBuilder buildCommand(String... command) { - ArrayList args = new ArrayList<>(); - args.add(COPIED_JAVA.toString()); - Collections.addAll(args, Utils.prependTestJavaOpts(command)); - return new ProcessBuilder(args); - } + /* + * Success cases + */ - private static void exerciseSecurity(int exitCode, String output, ProcessBuilder process) throws Exception { - OutputAnalyzer oa = ProcessTools.executeProcess(process); - oa.shouldHaveExitValue(exitCode) - .shouldContain(output); - - // extra checks on debug output - if (exitCode != 1) { - if (oa.getStderr().contains("overriding other security properties files!")) { - overrideDetected = true; - // master file is not in use - only provider properties are set in custom file - oa.shouldContain("security.provider.2=SunRsaSign") - .shouldNotContain(EXPECTED_DEBUG_OUTPUT) - .shouldNotContain(UNEXPECTED_DEBUG_OUTPUT); - } else { - oa.shouldContain(EXPECTED_DEBUG_OUTPUT) - .shouldNotContain(UNEXPECTED_DEBUG_OUTPUT); - } - } - } - - // exercise the -XshowSettings:security launcher - private static void exerciseShowSettingsSecurity(ProcessBuilder process) throws Exception { - OutputAnalyzer oa = ProcessTools.executeProcess(process); - oa.shouldHaveExitValue(0) + static void testShowSettings(Executor ex, FilesManager filesMgr) + throws Exception { + // Sanity test passing the -XshowSettings:security option. + ex.addJvmArg("-XshowSettings:security"); + ex.setMasterFile(filesMgr.newMasterFile()); + ex.assertSuccess(); + ex.getOutputAnalyzer() .shouldContain("Security properties:") .shouldContain("Security provider static configuration:") .shouldContain("Security TLS configuration"); } - private static void copyJDK(Path src, Path dst) throws Exception { - Files.walk(src) - .skip(1) - .forEach(file -> { + static void testIncludeBasic(Executor ex, FilesManager filesMgr) + throws Exception { + PropsFile masterFile = filesMgr.newMasterFile(); + ExtraPropsFile extraFile = filesMgr.newExtraFile(); + PropsFile file0 = filesMgr.newFile("file0.properties"); + PropsFile file1 = filesMgr.newFile("dir1/file1.properties"); + PropsFile file2 = filesMgr.newFile("dir1/dir2/file2.properties"); + + masterFile.addAbsoluteInclude(file0); + extraFile.addRelativeInclude(file2); + file2.addAbsoluteInclude(file1); + + ex.setMasterFile(masterFile); + ex.setExtraFile(extraFile, Executor.ExtraMode.FILE_URI, false); + ex.assertSuccess(); + } + + static void testRepeatedInclude(Executor ex, FilesManager filesMgr) + throws Exception { + PropsFile masterFile = filesMgr.newMasterFile(); + PropsFile file0 = filesMgr.newFile("file0.properties"); + PropsFile file1 = filesMgr.newFile("dir1/file1.properties"); + + masterFile.addAbsoluteInclude(file0); + masterFile.addAbsoluteInclude(file1); + masterFile.addAbsoluteInclude(file0); + file1.addRelativeInclude(file0); + + ex.setMasterFile(masterFile); + ex.assertSuccess(); + } + + static void testIncludeWithOverrideAll(Executor ex, FilesManager filesMgr) + throws Exception { + PropsFile masterFile = filesMgr.newMasterFile(); + ExtraPropsFile extraFile = filesMgr.newExtraFile(); + PropsFile file0 = filesMgr.newFile("file0.properties"); + PropsFile file1 = filesMgr.newFile("dir1/file1.properties"); + + masterFile.addRelativeInclude(file0); + extraFile.addAbsoluteInclude(file1); + + ex.setMasterFile(masterFile); + ex.setExtraFile(extraFile, Executor.ExtraMode.HTTP_SERVED, true); + ex.assertSuccess(); + } + + static void extraPropertiesByHelper(Executor ex, FilesManager filesMgr, + Executor.ExtraMode mode) throws Exception { + ExtraPropsFile extraFile = filesMgr.newExtraFile(); + PropsFile file0 = filesMgr.newFile("file0.properties"); + + extraFile.addRelativeInclude(file0); + + ex.setMasterFile(filesMgr.newMasterFile()); + ex.setExtraFile(extraFile, mode, true); + ex.assertSuccess(); + } + + static void testExtraPropertiesByPathAbsolute(Executor ex, + FilesManager filesMgr) throws Exception { + extraPropertiesByHelper(ex, filesMgr, Executor.ExtraMode.PATH_ABS); + } + + static void testExtraPropertiesByPathRelative(Executor ex, + FilesManager filesMgr) throws Exception { + extraPropertiesByHelper(ex, filesMgr, Executor.ExtraMode.PATH_REL); + } + + static void specialCharsIncludes(Executor ex, FilesManager filesMgr, + char specialChar, Executor.ExtraMode extraMode, + boolean useRelativeIncludes) throws Exception { + String suffix = specialChar + ".properties"; + ExtraPropsFile extraFile; + PropsFile file0, file1; + try { + extraFile = filesMgr.newExtraFile("extra" + suffix); + file0 = filesMgr.newFile("file0" + suffix); + file1 = filesMgr.newFile("file1" + suffix); + } catch (InvalidPathException ipe) { + // The platform encoding may not allow to create files with some + // special characters. Skip the test in these cases. + return; + } + + if (useRelativeIncludes) { + extraFile.addRelativeInclude(file0); + } else { + extraFile.addAbsoluteInclude(file0); + } + extraFile.addAbsoluteInclude(file1); + + ex.setMasterFile(filesMgr.newMasterFile()); + ex.setExtraFile(extraFile, extraMode, false); + ex.assertSuccess(); + } + + static void testUnicodeIncludes1(Executor ex, FilesManager filesMgr) + throws Exception { + specialCharsIncludes(ex, filesMgr, '\u2022', + Executor.ExtraMode.PATH_ABS, true); + } + + static void testUnicodeIncludes2(Executor ex, FilesManager filesMgr) + throws Exception { + specialCharsIncludes(ex, filesMgr, '\u2022', + Executor.ExtraMode.FILE_URI, true); + } + + static void testUnicodeIncludes3(Executor ex, FilesManager filesMgr) + throws Exception { + // Backward compatibility check. Malformed URLs such as + // file:/tmp/extra•.properties are supported for the extra file. + // However, relative includes are not allowed in these cases. + specialCharsIncludes(ex, filesMgr, '\u2022', + Executor.ExtraMode.RAW_FILE_URI1, false); + } + + static void testUnicodeIncludes4(Executor ex, FilesManager filesMgr) + throws Exception { + // Backward compatibility check. Malformed URLs such as + // file:///tmp/extra•.properties are supported for the extra file. + // However, relative includes are not allowed in these cases. + specialCharsIncludes(ex, filesMgr, '\u2022', + Executor.ExtraMode.RAW_FILE_URI2, false); + } + + static void testSpaceIncludes1(Executor ex, FilesManager filesMgr) + throws Exception { + specialCharsIncludes(ex, filesMgr, ' ', + Executor.ExtraMode.PATH_ABS, true); + } + + static void testSpaceIncludes2(Executor ex, FilesManager filesMgr) + throws Exception { + specialCharsIncludes(ex, filesMgr, ' ', + Executor.ExtraMode.FILE_URI, true); + } + + static void testSpaceIncludes3(Executor ex, FilesManager filesMgr) + throws Exception { + // Backward compatibility check. Malformed URLs such as + // file:/tmp/extra .properties are supported for the extra file. + // However, relative includes are not allowed in these cases. + specialCharsIncludes(ex, filesMgr, ' ', + Executor.ExtraMode.RAW_FILE_URI1, false); + } + + static void testSpaceIncludes4(Executor ex, FilesManager filesMgr) + throws Exception { + // Backward compatibility check. Malformed URLs such as + // file:///tmp/extra .properties are supported for the extra file. + // However, relative includes are not allowed in these cases. + specialCharsIncludes(ex, filesMgr, ' ', + Executor.ExtraMode.RAW_FILE_URI2, false); + } + + static void notOverrideOnFailureHelper(Executor ex, FilesManager filesMgr, + String nonExistentExtraFile) throws Exception { + // An overriding extra properties file that does not exist + // should not erase properties from the master file. + ex.setIgnoredExtraFile(nonExistentExtraFile, true); + ex.setMasterFile(filesMgr.newMasterFile()); + ex.assertSuccess(); + ex.getOutputAnalyzer().shouldContain("unable to load security " + + "properties from " + nonExistentExtraFile); + } + + static void testNotOverrideOnEmptyFailure(Executor ex, + FilesManager filesMgr) throws Exception { + notOverrideOnFailureHelper(ex, filesMgr, ""); + ex.getOutputAnalyzer() + .shouldContain("Empty extra properties file path"); + } + + static void testNotOverrideOnURLFailure(Executor ex, FilesManager filesMgr) + throws Exception { + notOverrideOnFailureHelper(ex, filesMgr, + "file:///nonExistentFile.properties"); + } + + static void testNotOverrideOnPathFailure(Executor ex, FilesManager filesMgr) + throws Exception { + notOverrideOnFailureHelper(ex, filesMgr, "nonExistentFile.properties"); + } + + static void testNotOverrideOnDirFailure(Executor ex, FilesManager filesMgr) + throws Exception { + notOverrideOnFailureHelper(ex, filesMgr, "file:///"); + ex.getOutputAnalyzer().shouldContain("Is a directory"); + } + + static void testNotOverrideOnBadFileURLFailure(Executor ex, + FilesManager filesMgr) throws Exception { + notOverrideOnFailureHelper(ex, filesMgr, "file:///%00"); + } + + static void testDisabledExtraPropertiesFile(Executor ex, + FilesManager filesMgr) throws Exception { + PropsFile masterFile = filesMgr.newMasterFile(); + PropsFile file0 = filesMgr.newFile("file0.properties"); + + masterFile.addRawProperty("security.overridePropertiesFile", "false"); + + ex.setMasterFile(masterFile); + ex.setIgnoredExtraFile(file0.path.toString(), true); + ex.assertSuccess(); + } + + static final String SECURITY_SET_PROP_FILE_PATH = + "testSecuritySetPropertyShouldNotInclude.propsFilePath"; + + static void testSecuritySetPropertyShouldNotInclude(Executor ex, + FilesManager filesMgr) throws Exception { + PropsFile masterFile = filesMgr.newMasterFile(); + PropsFile file0 = filesMgr.newFile("file0.properties"); + + ex.addSystemProp(SECURITY_SET_PROP_FILE_PATH, file0.path.toString()); + ex.setMasterFile(masterFile); + ex.assertSuccess(); + } + + static void assertTestSecuritySetPropertyShouldNotInclude() { + // This check is executed by the launched JVM. + String propsFilePath = System.getProperty(SECURITY_SET_PROP_FILE_PATH); + if (propsFilePath != null) { + String name = Path.of(propsFilePath).getFileName().toString(); + String setPropInvokeRepr = "Security.setProperty(\"include\", " + + "\"" + propsFilePath + "\")"; + try { + Security.setProperty("include", propsFilePath); + throw new RuntimeException(setPropInvokeRepr + " was " + + "expected to throw IllegalArgumentException."); + } catch (IllegalArgumentException expected) {} + if (FilesManager.APPLIED_PROP_VALUE.equals( + Security.getProperty(name))) { + throw new RuntimeException(setPropInvokeRepr + " caused " + + "a file inclusion."); + } + try { + Security.getProperty("include"); + throw new RuntimeException("Security.getProperty(\"include\")" + + " was expected to throw IllegalArgumentException."); + } catch (IllegalArgumentException expected) {} + } + } + + /* + * Error cases + */ + + static void testCannotResolveRelativeFromHTTPServed(Executor ex, + FilesManager filesMgr) throws Exception { + ExtraPropsFile extraFile = filesMgr.newExtraFile(); + PropsFile file0 = filesMgr.newFile("file0.properties"); + + extraFile.addRelativeInclude(file0); + + ex.setMasterFile(filesMgr.newMasterFile()); + ex.setExtraFile(extraFile, Executor.ExtraMode.HTTP_SERVED, true); + ex.assertError("InternalError: Cannot resolve '" + file0.fileName + + "' relative path when included from a non-regular " + + "properties file (e.g. HTTP served file)"); + } + + static void testCannotIncludeCycles(Executor ex, FilesManager filesMgr) + throws Exception { + PropsFile masterFile = filesMgr.newMasterFile(); + PropsFile file0 = filesMgr.newFile("file0.properties"); + PropsFile file1 = filesMgr.newFile("dir1/file1.properties"); + + // Includes chain: master -> file0 -> file1 -> master. + file1.addRelativeInclude(masterFile); + file0.addRelativeInclude(file1); + masterFile.addRelativeInclude(file0); + + ex.setMasterFile(masterFile); + ex.assertError( + "InternalError: Cyclic include of '" + masterFile.path + "'"); + } + + static void testCannotIncludeURL(Executor ex, FilesManager filesMgr) + throws Exception { + PropsFile masterFile = filesMgr.newMasterFile(); + ExtraPropsFile extraFile = filesMgr.newExtraFile(); + + masterFile.addRawProperty("include", extraFile.url.toString()); + + ex.setMasterFile(masterFile); + ex.assertError("InternalError: Unable to include 'http://127.0.0.1:"); + } + + static void testCannotIncludeNonexistentFile(Executor ex, + FilesManager filesMgr) throws Exception { + PropsFile masterFile = filesMgr.newMasterFile(); + + String nonexistentPath = "/nonExistentFile.properties"; + masterFile.addRawProperty("include", nonexistentPath); + + ex.setMasterFile(masterFile); + ex.assertError( + "InternalError: Unable to include '" + nonexistentPath + "'"); + } + + static void testMustHaveMasterFile(Executor ex, FilesManager filesMgr) + throws Exception { + // Launch a JDK without a master java.security file present. + ex.assertError("InternalError: Error loading java.security file"); + } + + static void testMustHaveMasterFileEvenWithExtraFile(Executor ex, + FilesManager filesMgr) throws Exception { + // Launch a JDK without a master java.security file present, but with an + // extra file passed. Since the "security.overridePropertiesFile=true" + // security property is missing, it should fail anyway. + ex.setExtraFile( + filesMgr.newExtraFile(), Executor.ExtraMode.FILE_URI, true); + ex.assertError("InternalError: Error loading java.security file"); + } +} + +sealed class PropsFile permits ExtraPropsFile { + protected static final class Include { + final PropsFile propsFile; + final String value; + + private Include(PropsFile propsFile, String value) { + this.propsFile = propsFile; + this.value = value; + } + + static Include of(PropsFile propsFile) { + return new Include(propsFile, propsFile.path.toString()); + } + + static Include of(PropsFile propsFile, String value) { + return new Include(propsFile, value); + } + } + + protected final List includes = new ArrayList<>(); + protected final PrintWriter writer; + protected boolean includedFromExtra = false; + final String fileName; + final Path path; + + PropsFile(String fileName, Path path) throws IOException { + this.fileName = fileName; + this.path = path; + this.writer = new PrintWriter(Files.newOutputStream(path, + StandardOpenOption.CREATE, StandardOpenOption.APPEND), true); + } + + private static String escape(String text, boolean escapeSpace) { + StringBuilder sb = new StringBuilder(text.length()); + CharBuffer cb = CharBuffer.wrap(text); + while (cb.hasRemaining()) { + char c = cb.get(); + if (c == '\\' || escapeSpace && c == ' ') { + sb.append('\\'); + } + if (Character.UnicodeBlock.of(c) == + Character.UnicodeBlock.BASIC_LATIN) { + sb.append(c); + } else { + sb.append("\\u%04x".formatted((int) c)); + } + } + return sb.toString(); + } + + private void addRawProperty(String key, String value, String sep) { + writer.println(escape(key, true) + sep + escape(value, false)); + } + + protected void addIncludeDefinition(Include include) { + if (include.propsFile instanceof ExtraPropsFile) { + throw new RuntimeException("ExtraPropsFile should not be included"); + } + includes.add(include); + addRawProperty("include", include.value, " "); + } + + void addComment(String comment) { + writer.println("# " + comment); + } + + void addRawProperty(String key, String value) { + addRawProperty(key, value, "="); + } + + void addAbsoluteInclude(PropsFile propsFile) { + addIncludeDefinition(Include.of(propsFile)); + } + + void addRelativeInclude(PropsFile propsFile) { + addIncludeDefinition(Include.of(propsFile, + path.getParent().relativize(propsFile.path).toString())); + } + + void assertApplied(OutputAnalyzer oa) { + oa.shouldContain(Executor.INITIAL_PROP_LOG_MSG + fileName + "=" + + FilesManager.APPLIED_PROP_VALUE); + for (Include include : includes) { + include.propsFile.assertApplied(oa); + oa.shouldContain("processing include: '" + include.value + "'"); + oa.shouldContain("finished processing " + include.propsFile.path); + } + } + + void assertWasOverwritten(OutputAnalyzer oa) { + oa.shouldNotContain(Executor.INITIAL_PROP_LOG_MSG + fileName + "=" + + FilesManager.APPLIED_PROP_VALUE); + for (Include include : includes) { + if (!include.propsFile.includedFromExtra) { + include.propsFile.assertWasOverwritten(oa); + } + oa.shouldContain("processing include: '" + include.value + "'"); + oa.shouldContain("finished processing " + include.propsFile.path); + } + } + + void markAsIncludedFromExtra() { + includedFromExtra = true; + for (Include include : includes) { + include.propsFile.markAsIncludedFromExtra(); + } + } + + PropsFile getLastFile() { + return includes.isEmpty() ? + this : includes.getLast().propsFile.getLastFile(); + } + + void close() { + writer.close(); + } +} + +final class ExtraPropsFile extends PropsFile { + private final Map systemProps = new LinkedHashMap<>(); + final URI url; + + ExtraPropsFile(String fileName, URI url, Path path) throws IOException { + super(fileName, path); + this.url = url; + } + + @Override + protected void addIncludeDefinition(Include include) { + if (includes.isEmpty()) { + String propName = "props.fileName"; + systemProps.put(propName, include.propsFile.fileName); + include = Include.of(include.propsFile, + include.value.replace(include.propsFile.fileName, + "${props.none}${" + propName + "}")); + } + include.propsFile.markAsIncludedFromExtra(); + super.addIncludeDefinition(include); + } + + Map getSystemProperties() { + return Collections.unmodifiableMap(systemProps); + } +} + +final class FilesManager implements Closeable { + private static final Path ROOT_DIR = + Path.of(ConfigFileTest.class.getSimpleName()).toAbsolutePath(); + private static final Path PROPS_DIR = ROOT_DIR.resolve("properties"); + private static final Path JDK_DIR = ROOT_DIR.resolve("jdk"); + private static final Path MASTER_FILE = + JDK_DIR.resolve("conf/security/java.security"); + private static final Path MASTER_FILE_TEMPLATE = + MASTER_FILE.resolveSibling("java.security.template"); + static final String JAVA_EXECUTABLE = + JDK_DIR.resolve("bin/java").toString(); + static final String LAST_FILE_PROP_NAME = "last-file"; + static final String APPLIED_PROP_VALUE = "applied"; + + private final List createdFiles; + private final Set fileNamesInUse; + private final HttpServer httpServer; + private final URI serverUri; + private final long masterFileLines; + + FilesManager() throws Exception { + createdFiles = new ArrayList<>(); + fileNamesInUse = new HashSet<>(); + httpServer = HttpServer.create( + new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); + httpServer.createContext("/", this::handleRequest); + InetSocketAddress address = httpServer.getAddress(); + httpServer.start(); + serverUri = new URI("http", null, address.getHostString(), + address.getPort(), null, null, null); + copyJDK(); + try (Stream s = Files.lines(MASTER_FILE_TEMPLATE)) { + masterFileLines = s.count(); + } + } + + private static void copyJDK() throws Exception { + Path testJDK = Path.of(Objects.requireNonNull( + System.getProperty("test.jdk"), "unspecified test.jdk")); + if (!Files.exists(testJDK)) { + throw new RuntimeException("test.jdk -> nonexistent JDK"); + } + Files.createDirectories(JDK_DIR); + try (Stream pathStream = Files.walk(testJDK)) { + pathStream.skip(1).forEach((Path file) -> { try { - Files.copy(file, dst.resolve(src.relativize(file)), StandardCopyOption.COPY_ATTRIBUTES); + Files.copy(file, JDK_DIR.resolve(testJDK.relativize(file)), + StandardCopyOption.COPY_ATTRIBUTES); } catch (IOException ioe) { throw new UncheckedIOException(ioe); } }); + } + Files.move(MASTER_FILE, MASTER_FILE_TEMPLATE); + } + + private void handleRequest(HttpExchange x) throws IOException { + String rawPath = x.getRequestURI().getRawPath(); + Path f = ROOT_DIR.resolve(x.getRequestURI().getPath().substring(1)); + int statusCode; + byte[] responseBody; + // Check for unescaped space, unresolved parent or backward slash. + if (rawPath.matches("^.*( |(\\.|%2[Ee]){2}|\\\\|%5[Cc]).*$")) { + statusCode = HttpURLConnection.HTTP_BAD_REQUEST; + responseBody = new byte[0]; + } else if (Files.isRegularFile(f)) { + x.getResponseHeaders().add("Content-type", "text/plain"); + statusCode = HttpURLConnection.HTTP_OK; + responseBody = Files.readAllBytes(f); + } else { + statusCode = HttpURLConnection.HTTP_NOT_FOUND; + responseBody = new byte[0]; + } + System.out.println("[" + Instant.now() + "] " + + getClass().getSimpleName() + ": " + + x.getRequestMethod() + " " + rawPath + " -> " + + statusCode + " (" + responseBody.length + " bytes)"); + try (OutputStream responseStream = x.getResponseBody()) { + x.sendResponseHeaders(statusCode, responseBody.length); + responseStream.write(responseBody); + } + } + + @FunctionalInterface + private interface PropsFileBuilder { + PropsFile build(String fileName, Path path) throws IOException; + } + + private PropsFile newFile(Path path, PropsFileBuilder builder) + throws IOException { + String fileName = path.getFileName().toString(); + if (!fileNamesInUse.add(fileName)) { + // Names must be unique in order for the special + // property = to work. + throw new RuntimeException(fileName + " is repeated"); + } + Files.createDirectories(path.getParent()); + PropsFile propsFile = builder.build(fileName, path); + propsFile.addComment("Property to determine if this properties file " + + "was parsed and not overwritten:"); + propsFile.addRawProperty(fileName, APPLIED_PROP_VALUE); + propsFile.addComment(ConfigFileTest.SEPARATOR_THIN); + propsFile.addComment("Property to be overwritten by every properties " + + "file (master, extra or included):"); + propsFile.addRawProperty(LAST_FILE_PROP_NAME, fileName); + propsFile.addComment(ConfigFileTest.SEPARATOR_THIN); + createdFiles.add(propsFile); + return propsFile; + } + + PropsFile newFile(String relPathStr) throws IOException { + return newFile(PROPS_DIR.resolve(relPathStr), PropsFile::new); + } + + PropsFile newMasterFile() throws IOException { + Files.copy(MASTER_FILE_TEMPLATE, MASTER_FILE); + return newFile(MASTER_FILE, PropsFile::new); + } + + ExtraPropsFile newExtraFile() throws IOException { + return newExtraFile("extra.properties"); + } + + ExtraPropsFile newExtraFile(String extraFileName) throws IOException { + return (ExtraPropsFile) newFile(PROPS_DIR.resolve(extraFileName), + (fileName, path) -> { + URI uri = serverUri.resolve(ParseUtil.encodePath( + ROOT_DIR.relativize(path).toString())); + return new ExtraPropsFile(fileName, uri, path); + }); + } + + void reportCreatedFiles() throws IOException { + for (PropsFile propsFile : createdFiles) { + System.err.println(); + System.err.println(propsFile.path.toString()); + System.err.println(ConfigFileTest.SEPARATOR_THIN.repeat(3)); + try (Stream lines = Files.lines(propsFile.path)) { + long lineNumber = 1L; + Iterator it = lines.iterator(); + while (it.hasNext()) { + String line = it.next(); + if (!propsFile.path.equals(MASTER_FILE) || + lineNumber > masterFileLines) { + System.err.println(line); + } + lineNumber++; + } + } + System.err.println(); + } + } + + void clear() throws IOException { + if (!createdFiles.isEmpty()) { + for (PropsFile propsFile : createdFiles) { + propsFile.close(); + Files.delete(propsFile.path); + } + FileUtils.deleteFileTreeUnchecked(PROPS_DIR); + createdFiles.clear(); + fileNamesInUse.clear(); + } + } + + @Override + public void close() throws IOException { + clear(); + httpServer.stop(0); + FileUtils.deleteFileTreeUnchecked(ROOT_DIR); + } +} + +final class Executor { + enum ExtraMode { + HTTP_SERVED, FILE_URI, RAW_FILE_URI1, RAW_FILE_URI2, PATH_ABS, PATH_REL + } + static final String RUNNER_ARG = "runner"; + static final String INITIAL_PROP_LOG_MSG = "Initial security property: "; + private static final String OVERRIDING_LOG_MSG = + "overriding other security properties files!"; + private static final String[] ALWAYS_UNEXPECTED_LOG_MSGS = { + "java.lang.AssertionError", + INITIAL_PROP_LOG_MSG + "postInitTest=shouldNotRecord", + INITIAL_PROP_LOG_MSG + "include=", + }; + private static final Path CWD = Path.of(".").toAbsolutePath(); + private static final String JAVA_SEC_PROPS = "java.security.properties"; + private static final String CLASS_PATH = Objects.requireNonNull( + System.getProperty("test.classes"), "unspecified test.classes"); + private static final String DEBUG_ARG = + "-Xrunjdwp:transport=dt_socket,address=localhost:8000,suspend=y"; + private final Map systemProps = new LinkedHashMap<>( + Map.of("java.security.debug", "all", "javax.net.debug", "all", + // Ensure we get UTF-8 debug outputs in Windows: + "stderr.encoding", "UTF-8", "stdout.encoding", "UTF-8")); + private final List jvmArgs = new ArrayList<>( + List.of(FilesManager.JAVA_EXECUTABLE, "-enablesystemassertions", + // Uncomment DEBUG_ARG to debug test-launched JVMs: + "-classpath", CLASS_PATH//, DEBUG_ARG + )); + private PropsFile masterPropsFile; + private ExtraPropsFile extraPropsFile; + private boolean expectedOverrideAll = false; + private OutputAnalyzer oa; + + static void run(Method m, FilesManager filesMgr) throws Exception { + try { + m.invoke(null, new Executor(), filesMgr); + } catch (Throwable e) { + filesMgr.reportCreatedFiles(); + throw e; + } finally { + filesMgr.clear(); + } + } + + void addSystemProp(String key, String value) { + systemProps.put(key, value); + } + + private void setRawExtraFile(String extraFile, boolean overrideAll) { + addSystemProp(JAVA_SEC_PROPS, (overrideAll ? "=" : "") + extraFile); + } + + void setMasterFile(PropsFile masterPropsFile) { + this.masterPropsFile = masterPropsFile; + } + + void setExtraFile(ExtraPropsFile extraPropsFile, ExtraMode mode, + boolean overrideAll) { + this.extraPropsFile = extraPropsFile; + expectedOverrideAll = overrideAll; + setRawExtraFile(switch (mode) { + case HTTP_SERVED -> extraPropsFile.url.toString(); + case FILE_URI -> extraPropsFile.path.toUri().toString(); + case RAW_FILE_URI1 -> "file:" + extraPropsFile.path; + case RAW_FILE_URI2 -> "file://" + + (extraPropsFile.path.startsWith("/") ? "" : "/") + + extraPropsFile.path; + case PATH_ABS -> extraPropsFile.path.toString(); + case PATH_REL -> CWD.relativize(extraPropsFile.path).toString(); + }, overrideAll); + } + + void setIgnoredExtraFile(String extraPropsFile, boolean overrideAll) { + setRawExtraFile(extraPropsFile, overrideAll); + expectedOverrideAll = false; + } + + void addJvmArg(String arg) { + jvmArgs.add(arg); + } + + private void execute(boolean successExpected) throws Exception { + List command = new ArrayList<>(jvmArgs); + Collections.addAll(command, Utils.getTestJavaOpts()); + addSystemPropertiesAsJvmArgs(command); + command.add(ConfigFileTest.class.getSimpleName()); + command.add(RUNNER_ARG); + oa = ProcessTools.executeProcess(new ProcessBuilder(command)); + oa.shouldHaveExitValue(successExpected ? 0 : 1); + for (String output : ALWAYS_UNEXPECTED_LOG_MSGS) { + oa.shouldNotContain(output); + } + } + + private void addSystemPropertiesAsJvmArgs(List command) { + Map allSystemProps = new LinkedHashMap<>(systemProps); + if (extraPropsFile != null) { + allSystemProps.putAll(extraPropsFile.getSystemProperties()); + } + for (Map.Entry e : allSystemProps.entrySet()) { + command.add("-D" + e.getKey() + "=" + e.getValue()); + } + } + + void assertSuccess() throws Exception { + execute(true); + + // Ensure every file was processed by checking a unique property used as + // a flag. Each file defines =applied. + // + // For example: + // + // file0 + // --------------- + // file0=applied + // include file1 + // + // file1 + // --------------- + // file1=applied + // + // The assertion would be file0 == applied AND file1 == applied. + // + if (extraPropsFile != null) { + extraPropsFile.assertApplied(oa); + } + if (expectedOverrideAll) { + // When overriding with an extra file, check that neither + // the master file nor its includes are visible. + oa.shouldContain(OVERRIDING_LOG_MSG); + masterPropsFile.assertWasOverwritten(oa); + } else { + oa.shouldNotContain(OVERRIDING_LOG_MSG); + masterPropsFile.assertApplied(oa); + } + + // Ensure the last included file overwrote a fixed property. Each file + // defines last-file=. + // + // For example: + // + // file0 + // --------------- + // last-file=file0 + // include file1 + // + // file1 + // --------------- + // last-file=file1 + // + // The assertion would be last-file == file1. + // + PropsFile lastFile = (extraPropsFile == null ? + masterPropsFile : extraPropsFile).getLastFile(); + oa.shouldContain(FilesManager.LAST_FILE_PROP_NAME + "=" + + lastFile.fileName); + oa.stdoutShouldContain(FilesManager.LAST_FILE_PROP_NAME + ": " + + lastFile.fileName); + } + + void assertError(String message) throws Exception { + execute(false); + oa.shouldContain(message); + } + + OutputAnalyzer getOutputAnalyzer() { + return oa; } } diff --git a/test/jdk/java/security/Security/override.props b/test/jdk/java/security/Security/override.props deleted file mode 100644 index d0190f576fd..00000000000 --- a/test/jdk/java/security/Security/override.props +++ /dev/null @@ -1,7 +0,0 @@ -# exercise ServiceLoader and legacy (class load) approach -security.provider.1=sun.security.provider.Sun -security.provider.2=SunRsaSign -security.provider.3=sun.security.ssl.SunJSSE -security.provider.4=com.sun.crypto.provider.SunJCE -security.provider.5=SunJGSS -security.provider.6=SunSASL \ No newline at end of file diff --git a/test/jdk/java/security/Security/signedfirst/DynStatic.java b/test/jdk/java/security/Security/signedfirst/DynStatic.java index 59e30de5462..17fab4cac5b 100644 --- a/test/jdk/java/security/Security/signedfirst/DynStatic.java +++ b/test/jdk/java/security/Security/signedfirst/DynStatic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, 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 @@ -33,7 +33,6 @@ import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; import jdk.test.lib.compiler.CompilerUtils; import jdk.test.lib.process.ProcessTools; @@ -52,9 +51,8 @@ public class DynStatic { Paths.get(TEST_SRC, "DynSignedProvFirst.java"); private static final Path STATIC_SRC = Paths.get(TEST_SRC, "StaticSignedProvFirst.java"); - - private static final String STATIC_PROPS = - Paths.get(TEST_SRC, "Static.props").toString(); + private static final Path STATIC_PROPS = + Paths.get(TEST_SRC, "Static.props"); public static void main(String[] args) throws Exception { @@ -89,7 +87,7 @@ public class DynStatic { // Run the StaticSignedProvFirst test program ProcessTools.executeTestJava("-classpath", TEST_CLASSES.toString() + File.pathSeparator + "exp.jar", - "-Djava.security.properties=file:" + STATIC_PROPS, + "-Djava.security.properties=" + STATIC_PROPS.toUri(), "StaticSignedProvFirst") .shouldContain("test passed"); }