Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.security.Provider.Service;
import java.time.Clock;
import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
Expand Down Expand Up @@ -63,6 +64,8 @@ public final class RestrictedSecurity {
private static boolean isFIPSEnabled;

private static final boolean allowSetProperties;
private static final boolean suppressSunsetWarning;
private static final boolean ignoreSunsetExpiration;

private static final boolean isNSSSupported;
private static final boolean isOpenJCEPlusCertifiedPlatform;
Expand Down Expand Up @@ -104,6 +107,8 @@ public final class RestrictedSecurity {

userEnabledFIPS = Boolean.getBoolean("semeru.fips");
allowSetProperties = Boolean.getBoolean("semeru.fips.allowsetproperties");
suppressSunsetWarning = getBooleanPropertyTreatEmptyAsTrue("semeru.restrictedsecurity.suppresssunsetwarning");
ignoreSunsetExpiration = getBooleanPropertyTreatEmptyAsTrue("semeru.restrictedsecurity.ignoresunsetexpiration");

if (userEnabledFIPS) {
if (isFIPSSupported) {
Expand Down Expand Up @@ -135,6 +140,19 @@ private RestrictedSecurity() {
super();
}

private static boolean getBooleanPropertyTreatEmptyAsTrue(String name) {
String systemProp = System.getProperty(name);
if (systemProp == null) {
return false;
}
if (systemProp.isEmpty()) {
// If set without a value (e.g., "-Dsemeru.restrictedsecurity.suppresssunsetwarning"),
// treat it as true and suppress the warning.
return true;
}
return Boolean.parseBoolean(systemProp);
}

private static boolean isJarVerifierInStackTrace() {
java.util.function.Predicate<Class<?>> isJarVerifier =
clazz -> "java.util.jar.JarVerifier".equals(clazz.getName())
Expand Down Expand Up @@ -612,9 +630,34 @@ private static void restrictsCheck() {
printStackTraceAndExit("Restricted security property is null.");
}

// Check if the SunsetDate expired.
if (isPolicySunset(restricts.descSunsetDate)) {
printStackTraceAndExit("Restricted security policy expired.");
int expireMonths = monthsToPolicySunset(restricts.descSunsetDate);

if (suppressSunsetWarning) {
if ((expireMonths <= 0) && !ignoreSunsetExpiration) {
System.exit(1);
}
} else if (expireMonths <= 0) { // Check if the SunsetDate expired.
if (ignoreSunsetExpiration) {
System.err.format("The requested restricted security profile %s expired on %s"
+ ": Certified cryptography use cannot be guaranteed.%n"
+ "Use -Dsemeru.restrictedsecurity.suppresssunsetwarning to stop displaying this message.%n"
+ "The -Dsemeru.restrictedsecurity.ignoresunsetexpiration option has been specified.%n"
+ "WARNING: Java will start with the requested restricted security profile but uncertified"
+ " cryptography may be active.%n",
restricts.profileID, restricts.descSunsetDate);
} else {
printStackTraceAndExit(String.format("The requested restricted security profile %s expired on %s"
+ ": Java will stop because certified cryptography use cannot be guaranteed.%n"
+ "Use -Dsemeru.restrictedsecurity.suppresssunsetwarning to stop displaying this message.%n"
+ "Use -Dsemeru.restrictedsecurity.ignoresunsetexpiration to allow Java to start while"
+ " possibly using uncertified cryptography.%n",
restricts.profileID, restricts.descSunsetDate));
}
} else if (expireMonths <= 6) { // Check if the SunsetDate will expire within 6 months.
System.err.format("The restricted security profile %s will expire on %s,"
+ " after which Java will fail to start if this profile is specified.%n"
+ "The latest Semeru Runtimes release may include an updated security profile.%n",
restricts.profileID, restricts.descSunsetDate);
}

// Check secure random settings.
Expand All @@ -633,25 +676,35 @@ private static void restrictsCheck() {
* Check if restricted security policy is sunset.
*
* @param descSunsetDate the sunset date from java.security
* @return true if restricted security policy sunset
* @return the number of months until restricted security policy sunset
*/
private static boolean isPolicySunset(String descSunsetDate) {
boolean isSunset = false;
// Only check if a sunset date is specified in the profile.
if (!isNullOrBlank(descSunsetDate)) {
try {
isSunset = LocalDate.parse(descSunsetDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"))
.isBefore(LocalDate.now(Clock.systemUTC()));
} catch (DateTimeParseException except) {
printStackTraceAndExit(
"Restricted security policy sunset date is incorrect, the correct format is yyyy-MM-dd.");
}
private static int monthsToPolicySunset(String descSunsetDate) {
// If no sunset date is specified, it will not sunset.
if (isNullOrBlank(descSunsetDate)) {
return Integer.MAX_VALUE;
}

if (debug != null) {
debug.println("Restricted security policy is sunset: " + isSunset);
LocalDate sunsetDate;
try {
sunsetDate = LocalDate.parse(descSunsetDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
} catch (DateTimeParseException e) {
printStackTraceAndExit(
"Restricted security policy sunset date is incorrect, the correct format is yyyy-MM-dd.");
return 0;
}

LocalDate now = LocalDate.now(Clock.systemUTC());

if (sunsetDate.isBefore(now)) {
return 0; // Already sunset.
}

Period period = Period.between(now, sunsetDate);
int months = (period.getYears() * 12) + period.getMonths();
if (period.getDays() > 0) {
months += 1;
}
return isSunset;
return months;
}

/**
Expand Down
180 changes: 180 additions & 0 deletions closed/test/jdk/openj9/internal/security/TestPolicySunset.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* ===========================================================================
* (c) Copyright IBM Corp. 2025, 2025 All Rights Reserved
* ===========================================================================
*
* 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.
*
* IBM designates this particular file as subject to the "Classpath" exception
* as provided by IBM 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, see <http://www.gnu.org/licenses/>.
*
* ===========================================================================
*/

/*
* @test
* @summary Test Restricted Security Mode Policy Sunset
* @library /test/lib
* @run junit TestPolicySunset
*/

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Provider;
import java.security.Security;
import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;

public class TestPolicySunset {

private static Path updateExpireSoonSunsetFile(String baseFile) {
try {
LocalDate soonDate = LocalDate.now(Clock.systemUTC()).plusMonths(1);
String newDate = soonDate.format(DateTimeFormatter.ISO_DATE);

String content = Files.readString(Paths.get(baseFile), StandardCharsets.UTF_8);
String pattern = "(?m)^(RestrictedSecurity\\.Test-Profile-PolicySunset-ExpireSoon\\.desc\\.sunsetDate)\\s*=.*$";
String updated = content.replaceAll(pattern, "$1 = " + newDate);

Path tmp = Files.createTempFile("sunset-java.security.expireSoon.", ".tmp");
Files.writeString(tmp, updated, StandardCharsets.UTF_8);
return tmp;
} catch (IOException e) {
throw new RuntimeException("Failed to update sunset date for ExpireSoon profile", e);
}
}

private static Stream<Arguments> patternMatches_testPolicySunset() {
String propertyFile = System.getProperty("test.src") + "/sunset-java.security";
String updatedPropertyFile = updateExpireSoonSunsetFile(propertyFile).toString();

return Stream.of(
// 1 - expired; suppress=false; ignore=true
Arguments.of("Test-Profile-PolicySunset-Expired",
propertyFile,
"=false", "=true",
"WARNING: Java will start with the requested restricted security profile but uncertified cryptography may be active",
0),
// 2 - expired; suppress=true; ignore=true, no warning
Arguments.of("Test-Profile-PolicySunset-Expired",
propertyFile,
"=true", "=true",
"",
0),
// 3 - expire soon (<=6 months); suppress=false
Arguments.of("Test-Profile-PolicySunset-ExpireSoon",
updatedPropertyFile,
"=false", "=false",
"The restricted security profile RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon will expire",
0),
// 4 - expire soon (<=6 months); suppress=true, no warning
Arguments.of("Test-Profile-PolicySunset-ExpireSoon",
updatedPropertyFile,
"=true", "=false",
"",
0),
// 5 - not expire (>6 months); no warning
Arguments.of("Test-Profile-PolicySunset-NotExpire",
propertyFile,
"=false", "=false",
"",
0),
// 6 - expired; property treat empty as true, no warning
Arguments.of("Test-Profile-PolicySunset-Expired",
propertyFile,
"", "",
"",
0),
// 7 - expired; suppress unset, ignore=true
Arguments.of("Test-Profile-PolicySunset-Expired",
propertyFile,
null, "=true",
"WARNING: Java will start with the requested restricted security profile but uncertified cryptography may be active",
0),
// 8 - expired; suppress=false; ignore=false
Arguments.of("Test-Profile-PolicySunset-Expired",
propertyFile,
"=false", "=false",
"Use -Dsemeru.restrictedsecurity.ignoresunsetexpiration to allow Java to start while possibly using uncertified cryptography",
1),
// 9 - expired; suppress=true; ignore=false, no warning
Arguments.of("Test-Profile-PolicySunset-Expired",
propertyFile,
"=true", "=false",
"",
1),
// 10 - expired; suppress=true, ignore unset, no warning
Arguments.of("Test-Profile-PolicySunset-Expired",
propertyFile,
"=true", null,
"",
1),
// 11 - expired; suppress=false; ignore unset
Arguments.of("Test-Profile-PolicySunset-Expired",
propertyFile,
"=false", null,
"Use -Dsemeru.restrictedsecurity.ignoresunsetexpiration to allow Java to start while possibly using uncertified cryptography",
1));
}

@ParameterizedTest
@MethodSource("patternMatches_testPolicySunset")
public void shouldContain_testPolicySunset(String customprofile, String securityPropertyFile,
String suppresssunsetwarning, String ignoresunsetexpiration, String expected, int exitValue)
throws Exception {
List<String> args = new ArrayList<>();

args.add("-Dsemeru.fips=true");
args.add("-Dsemeru.customprofile=" + customprofile);
args.add("-Djava.security.properties=" + securityPropertyFile);
if (suppresssunsetwarning != null) {
args.add("-Dsemeru.restrictedsecurity.suppresssunsetwarning" + suppresssunsetwarning);
}
if (ignoresunsetexpiration != null) {
args.add("-Dsemeru.restrictedsecurity.ignoresunsetexpiration" + ignoresunsetexpiration);
}
args.add("TestPolicySunset");

OutputAnalyzer outputAnalyzer = ProcessTools.executeTestJava(args);
outputAnalyzer.reportDiagnosticSummary();
outputAnalyzer.shouldHaveExitValue(exitValue).shouldMatch(expected);
}

public static void main(String[] args) {
// Something to trigger "properties" debug output.
try {
for (Provider provider : Security.getProviders()) {
System.out.println("Provider Name: " + provider.getName());
System.out.println("Provider Version: " + provider.getVersionStr());
}
} catch (Exception e) {
System.out.println(e);
}
}
}
Loading