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:
Francisco Ferrari Bihurriet 2024-09-23 17:45:38 +00:00
parent 0f9f777520
commit c6f1d5f374
6 changed files with 1178 additions and 238 deletions
src/java.base/share
classes
java/security
sun/security/util
conf/security
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");
}