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