/*
* 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.security;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
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.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;
/**
*
This class centralizes all security properties and common security
* methods. One of its primary uses is to manage providers.
*
*
The default values of security properties are read from an
* implementation-specific location, which is typically the properties file
* {@code conf/security/java.security} in the Java installation directory.
*
* @implNote If the properties file fails to load, the JDK implementation will
* throw an unspecified error when initializing the {@code Security} class.
*
* @author Benjamin Renaud
* @since 1.1
*/
public final class Security {
/* Are we debugging? -- for developers */
private static final Debug sdebug =
Debug.getInstance("properties");
/* The java.security properties */
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;
// An element in the cache
private static class ProviderProperty {
String className;
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 {
initialize();
// Set up JavaSecurityPropertiesAccess in SharedSecrets
SharedSecrets.setJavaSecurityPropertiesAccess(new JavaSecurityPropertiesAccess() {
@Override
public Properties getInitialProperties() {
return initialSecurityProperties;
}
});
}
private static void initialize() {
SecPropLoader.loadAll();
initialSecurityProperties = (Properties) props.clone();
if (sdebug != null) {
for (String key : props.stringPropertyNames()) {
sdebug.println("Initial security property: " + key + "=" +
props.getProperty(key));
}
}
}
/**
* Don't let anyone instantiate this.
*/
private Security() {
}
/**
* Looks up providers, and returns the property (and its associated
* provider) mapping the key, if any.
* The order in which the providers are looked up is the
* provider-preference order, as specified in the security
* properties file.
*/
private static ProviderProperty getProviderProperty(String key) {
List providers = Providers.getProviderList().providers();
for (int i = 0; i < providers.size(); i++) {
String matchKey;
Provider prov = providers.get(i);
String prop = prov.getProperty(key);
if (prop == null) {
// Is there a match if we do a case-insensitive property name
// comparison? Let's try ...
for (Enumeration