8319332: Security properties files inclusion
Co-authored-by: Francisco Ferrari Bihurriet <fferrari@openjdk.org> Co-authored-by: Martin Balao <mbalao@openjdk.org> Reviewed-by: weijun, mullan, kdriver
This commit is contained in:
parent
0f9f777520
commit
c6f1d5f374
src/java.base/share
test/jdk/java/security/Security
@ -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.*;
|
||||
|
||||
/**
|
||||
* <p>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<Path> 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<Object>) () -> {
|
||||
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. */
|
||||
|
@ -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<String> 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
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user