Skip to content

Commit 3d470d7

Browse files
committed
Implement sunset policy handling with suppress and ignore options
Added logic to handle restricted security profile sunset behavior using -Dsemeru.restrictedsecurity.suppresssunsetwarning and -Dsemeru.restrictedsecurity.ignoresunsetexpiration system properties. When suppresssunsetwarning is true, all sunset warning messages are suppressed; if the profile is expired and ignoresunsetexpiration is false, the JVM exits silently with status 1. When suppresssunsetwarning is false. If the profile has expired and ignoresunsetexpiration is false, a fatal error is printed and the JVM terminates. If expired and ignoresunsetexpiration is true, a warning message is printed indicating uncertified cryptography may be active. If the profile will expire within six months, a generic warning message is printed. Signed-off-by: Tao Liu <[email protected]>
1 parent ad35f2b commit 3d470d7

File tree

4 files changed

+321
-19
lines changed

4 files changed

+321
-19
lines changed

closed/src/java.base/share/classes/openj9/internal/security/RestrictedSecurity.java

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.security.Provider.Service;
3131
import java.time.Clock;
3232
import java.time.LocalDate;
33+
import java.time.Period;
3334
import java.time.format.DateTimeFormatter;
3435
import java.time.format.DateTimeParseException;
3536
import java.util.ArrayList;
@@ -63,6 +64,8 @@ public final class RestrictedSecurity {
6364
private static boolean isFIPSEnabled;
6465

6566
private static final boolean allowSetProperties;
67+
private static final boolean suppressSunsetWarning;
68+
private static final boolean ignoreSunsetExpiration;
6669

6770
private static final boolean isNSSSupported;
6871
private static final boolean isOpenJCEPlusCertifiedPlatform;
@@ -104,6 +107,8 @@ public final class RestrictedSecurity {
104107

105108
userEnabledFIPS = Boolean.getBoolean("semeru.fips");
106109
allowSetProperties = Boolean.getBoolean("semeru.fips.allowsetproperties");
110+
suppressSunsetWarning = getBooleanPropertyTreatEmptyAsTrue("semeru.restrictedsecurity.suppresssunsetwarning");
111+
ignoreSunsetExpiration = getBooleanPropertyTreatEmptyAsTrue("semeru.restrictedsecurity.ignoresunsetexpiration");
107112

108113
if (userEnabledFIPS) {
109114
if (isFIPSSupported) {
@@ -135,6 +140,19 @@ private RestrictedSecurity() {
135140
super();
136141
}
137142

143+
private static boolean getBooleanPropertyTreatEmptyAsTrue(String name) {
144+
String systemProp = System.getProperty(name);
145+
if (systemProp == null) {
146+
return false;
147+
}
148+
if (systemProp.isEmpty()) {
149+
// If set without a value (e.g., "-Dsemeru.restrictedsecurity.suppresssunsetwarning"),
150+
// treat it as true and suppress the warning.
151+
return true;
152+
}
153+
return Boolean.parseBoolean(systemProp);
154+
}
155+
138156
private static boolean isJarVerifierInStackTrace() {
139157
java.util.function.Predicate<Class<?>> isJarVerifier =
140158
clazz -> "java.util.jar.JarVerifier".equals(clazz.getName())
@@ -612,9 +630,34 @@ private static void restrictsCheck() {
612630
printStackTraceAndExit("Restricted security property is null.");
613631
}
614632

615-
// Check if the SunsetDate expired.
616-
if (isPolicySunset(restricts.descSunsetDate)) {
617-
printStackTraceAndExit("Restricted security policy expired.");
633+
int expireMonths = monthsToPolicySunset(restricts.descSunsetDate);
634+
635+
if (suppressSunsetWarning) {
636+
if ((expireMonths <= 0) && !ignoreSunsetExpiration) {
637+
System.exit(1);
638+
}
639+
} else if (expireMonths <= 0) { // Check if the SunsetDate expired.
640+
if (ignoreSunsetExpiration) {
641+
System.err.format("The requested restricted security profile %s expired on %s"
642+
+ ": Certified cryptography use cannot be guaranteed.%n"
643+
+ "Use -Dsemeru.restrictedsecurity.suppresssunsetwarning to stop displaying this message.%n"
644+
+ "The -Dsemeru.restrictedsecurity.ignoresunsetexpiration option has been specified.%n"
645+
+ "WARNING: Java will start with the requested restricted security profile but uncertified"
646+
+ " cryptography may be active.%n",
647+
restricts.profileID, restricts.descSunsetDate);
648+
} else {
649+
printStackTraceAndExit(String.format("The requested restricted security profile %s expired on %s"
650+
+ ": Java will stop because certified cryptography use cannot be guaranteed.%n"
651+
+ "Use -Dsemeru.restrictedsecurity.suppresssunsetwarning to stop displaying this message.%n"
652+
+ "Use -Dsemeru.restrictedsecurity.ignoresunsetexpiration to allow Java to start while"
653+
+ " possibly using uncertified cryptography.%n",
654+
restricts.profileID, restricts.descSunsetDate));
655+
}
656+
} else if (expireMonths <= 6) { // Check if the SunsetDate will expire within 6 months.
657+
System.err.format("The restricted security profile %s will expire on %s,"
658+
+ " after which Java will fail to start if this profile is specified.%n"
659+
+ "The latest Semeru Runtimes release may include an updated security profile.%n",
660+
restricts.profileID, restricts.descSunsetDate);
618661
}
619662

620663
// Check secure random settings.
@@ -633,25 +676,35 @@ private static void restrictsCheck() {
633676
* Check if restricted security policy is sunset.
634677
*
635678
* @param descSunsetDate the sunset date from java.security
636-
* @return true if restricted security policy sunset
679+
* @return the number of months until restricted security policy sunset
637680
*/
638-
private static boolean isPolicySunset(String descSunsetDate) {
639-
boolean isSunset = false;
640-
// Only check if a sunset date is specified in the profile.
641-
if (!isNullOrBlank(descSunsetDate)) {
642-
try {
643-
isSunset = LocalDate.parse(descSunsetDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"))
644-
.isBefore(LocalDate.now(Clock.systemUTC()));
645-
} catch (DateTimeParseException except) {
646-
printStackTraceAndExit(
647-
"Restricted security policy sunset date is incorrect, the correct format is yyyy-MM-dd.");
648-
}
681+
private static int monthsToPolicySunset(String descSunsetDate) {
682+
// If no sunset date is specified, it will not sunset.
683+
if (isNullOrBlank(descSunsetDate)) {
684+
return Integer.MAX_VALUE;
649685
}
650686

651-
if (debug != null) {
652-
debug.println("Restricted security policy is sunset: " + isSunset);
687+
LocalDate sunsetDate;
688+
try {
689+
sunsetDate = LocalDate.parse(descSunsetDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
690+
} catch (DateTimeParseException e) {
691+
printStackTraceAndExit(
692+
"Restricted security policy sunset date is incorrect, the correct format is yyyy-MM-dd.");
693+
return 0;
694+
}
695+
696+
LocalDate now = LocalDate.now(Clock.systemUTC());
697+
698+
if (sunsetDate.isBefore(now)) {
699+
return 0; // Already sunset.
700+
}
701+
702+
Period period = Period.between(now, sunsetDate);
703+
int months = (period.getYears() * 12) + period.getMonths();
704+
if (period.getDays() > 0) {
705+
months += 1;
653706
}
654-
return isSunset;
707+
return months;
655708
}
656709

657710
/**
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* ===========================================================================
3+
* (c) Copyright IBM Corp. 2025, 2025 All Rights Reserved
4+
* ===========================================================================
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation.
9+
*
10+
* IBM designates this particular file as subject to the "Classpath" exception
11+
* as provided by IBM in the LICENSE file that accompanied this code.
12+
*
13+
* This code is distributed in the hope that it will be useful, but WITHOUT
14+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16+
* version 2 for more details (a copy is included in the LICENSE file that
17+
* accompanied this code).
18+
*
19+
* You should have received a copy of the GNU General Public License version
20+
* 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
21+
*
22+
* ===========================================================================
23+
*/
24+
25+
/*
26+
* @test
27+
* @summary Test Restricted Security Mode Policy Sunset
28+
* @library /test/lib
29+
* @run junit TestPolicySunset
30+
*/
31+
32+
import org.junit.jupiter.params.ParameterizedTest;
33+
import org.junit.jupiter.params.provider.Arguments;
34+
import org.junit.jupiter.params.provider.MethodSource;
35+
36+
import java.io.IOException;
37+
import java.nio.charset.StandardCharsets;
38+
import java.nio.file.Files;
39+
import java.nio.file.Path;
40+
import java.nio.file.Paths;
41+
import java.security.Provider;
42+
import java.security.Security;
43+
import java.time.Clock;
44+
import java.time.LocalDate;
45+
import java.time.ZoneOffset;
46+
import java.time.format.DateTimeFormatter;
47+
import java.util.ArrayList;
48+
import java.util.List;
49+
import java.util.stream.Stream;
50+
51+
import jdk.test.lib.process.OutputAnalyzer;
52+
import jdk.test.lib.process.ProcessTools;
53+
54+
public class TestPolicySunset {
55+
56+
private static Path updateExpireSoonSunsetFile(String baseFile) {
57+
try {
58+
LocalDate soonDate = LocalDate.now(Clock.systemUTC()).plusMonths(1);
59+
String newDate = soonDate.format(DateTimeFormatter.ISO_DATE);
60+
61+
String content = Files.readString(Paths.get(baseFile), StandardCharsets.UTF_8);
62+
String pattern = "(?m)^(RestrictedSecurity\\.Test-Profile-PolicySunset-ExpireSoon\\.desc\\.sunsetDate)\\s*=.*$";
63+
String updated = content.replaceAll(pattern, "$1 = " + newDate);
64+
65+
Path tmp = Files.createTempFile("sunset-java.security.expireSoon.", ".tmp");
66+
Files.writeString(tmp, updated, StandardCharsets.UTF_8);
67+
return tmp;
68+
} catch (IOException e) {
69+
throw new RuntimeException("Failed to update sunset date for ExpireSoon profile", e);
70+
}
71+
}
72+
73+
private static Stream<Arguments> patternMatches_testPolicySunset() {
74+
String propertyFile = System.getProperty("test.src") + "/sunset-java.security";
75+
String updatedPropertyFile = updateExpireSoonSunsetFile(propertyFile).toString();
76+
77+
return Stream.of(
78+
// 1 - expired; suppress=false; ignore=true
79+
Arguments.of("Test-Profile-PolicySunset-Expired",
80+
propertyFile,
81+
"=false", "=true",
82+
"WARNING: Java will start with the requested restricted security profile but uncertified cryptography may be active",
83+
0),
84+
// 2 - expired; suppress=true; ignore=true, no warning
85+
Arguments.of("Test-Profile-PolicySunset-Expired",
86+
propertyFile,
87+
"=true", "=true",
88+
"",
89+
0),
90+
// 3 - expire soon (<=6 months); suppress=false
91+
Arguments.of("Test-Profile-PolicySunset-ExpireSoon",
92+
updatedPropertyFile,
93+
"=false", "=false",
94+
"The restricted security profile RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon will expire",
95+
0),
96+
// 4 - expire soon (<=6 months); suppress=true, no warning
97+
Arguments.of("Test-Profile-PolicySunset-ExpireSoon",
98+
updatedPropertyFile,
99+
"=true", "=false",
100+
"",
101+
0),
102+
// 5 - not expire (>6 months); no warning
103+
Arguments.of("Test-Profile-PolicySunset-NotExpire",
104+
propertyFile,
105+
"=false", "=false",
106+
"",
107+
0),
108+
// 6 - expired; property treat empty as true, no warning
109+
Arguments.of("Test-Profile-PolicySunset-Expired",
110+
propertyFile,
111+
"", "",
112+
"",
113+
0),
114+
// 7 - expired; suppress unset, ignore=true
115+
Arguments.of("Test-Profile-PolicySunset-Expired",
116+
propertyFile,
117+
null, "=true",
118+
"WARNING: Java will start with the requested restricted security profile but uncertified cryptography may be active",
119+
0),
120+
// 8 - expired; suppress=false; ignore=false
121+
Arguments.of("Test-Profile-PolicySunset-Expired",
122+
propertyFile,
123+
"=false", "=false",
124+
"Use -Dsemeru.restrictedsecurity.ignoresunsetexpiration to allow Java to start while possibly using uncertified cryptography",
125+
1),
126+
// 9 - expired; suppress=true; ignore=false, no warning
127+
Arguments.of("Test-Profile-PolicySunset-Expired",
128+
propertyFile,
129+
"=true", "=false",
130+
"",
131+
1),
132+
// 10 - expired; suppress=true, ignore unset, no warning
133+
Arguments.of("Test-Profile-PolicySunset-Expired",
134+
propertyFile,
135+
"=true", null,
136+
"",
137+
1),
138+
// 11 - expired; suppress=false; ignore unset
139+
Arguments.of("Test-Profile-PolicySunset-Expired",
140+
propertyFile,
141+
"=false", null,
142+
"Use -Dsemeru.restrictedsecurity.ignoresunsetexpiration to allow Java to start while possibly using uncertified cryptography",
143+
1));
144+
}
145+
146+
@ParameterizedTest
147+
@MethodSource("patternMatches_testPolicySunset")
148+
public void shouldContain_testPolicySunset(String customprofile, String securityPropertyFile,
149+
String suppresssunsetwarning, String ignoresunsetexpiration, String expected, int exitValue)
150+
throws Exception {
151+
List<String> args = new ArrayList<>();
152+
153+
args.add("-Dsemeru.fips=true");
154+
args.add("-Dsemeru.customprofile=" + customprofile);
155+
args.add("-Djava.security.properties=" + securityPropertyFile);
156+
if (suppresssunsetwarning != null) {
157+
args.add("-Dsemeru.restrictedsecurity.suppresssunsetwarning" + suppresssunsetwarning);
158+
}
159+
if (ignoresunsetexpiration != null) {
160+
args.add("-Dsemeru.restrictedsecurity.ignoresunsetexpiration" + ignoresunsetexpiration);
161+
}
162+
args.add("TestPolicySunset");
163+
164+
OutputAnalyzer outputAnalyzer = ProcessTools.executeTestJava(args);
165+
outputAnalyzer.reportDiagnosticSummary();
166+
outputAnalyzer.shouldHaveExitValue(exitValue).shouldMatch(expected);
167+
}
168+
169+
public static void main(String[] args) {
170+
// Something to trigger "properties" debug output.
171+
try {
172+
for (Provider provider : Security.getProviders()) {
173+
System.out.println("Provider Name: " + provider.getName());
174+
System.out.println("Provider Version: " + provider.getVersionStr());
175+
}
176+
} catch (Exception e) {
177+
System.out.println(e);
178+
}
179+
}
180+
}

closed/test/jdk/openj9/internal/security/TestProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private static Stream<Arguments> patternMatches_expectedExitValue1() {
120120
// 15 - Test property - policy sunset.
121121
Arguments.of("Test-Profile-PolicySunset.Base",
122122
System.getProperty("test.src") + "/property-java.security",
123-
"Restricted security policy expired"),
123+
"Use -Dsemeru.restrictedsecurity.ignoresunsetexpiration to allow Java to start while possibly using uncertified cryptography"),
124124
// 16 - Test property - policy sunset format.
125125
Arguments.of("Test-Profile-PolicySunsetFormat.Base",
126126
System.getProperty("test.src") + "/property-java.security",
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# ===========================================================================
2+
# (c) Copyright IBM Corp. 2025, 2025 All Rights Reserved
3+
# ===========================================================================
4+
# This code is free software; you can redistribute it and/or modify it
5+
# under the terms of the GNU General Public License version 2 only, as
6+
# published by the Free Software Foundation.
7+
#
8+
# IBM designates this particular file as subject to the "Classpath" exception
9+
# as provided by IBM in the LICENSE file that accompanied this code.
10+
#
11+
# This code is distributed in the hope that it will be useful, but WITHOUT
12+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
# version 2 for more details (a copy is included in the LICENSE file that
15+
# accompanied this code).
16+
#
17+
# You should have received a copy of the GNU General Public License version
18+
# 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
19+
# ===========================================================================
20+
21+
#
22+
# Test-Profile-PolicySunset-Expired
23+
#
24+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.name = Test-Profile-PolicySunset-Expired
25+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.default = true
26+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.fips = true
27+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.hash = SHA256:365fb3fe2ff69961980d10d16bd76f6efbf07a723b7a8c58a5f6cd234108eaee
28+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.number = Certificate #XXX
29+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.policy = https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/
30+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.sunsetDate = 2023-09-21
31+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.fips.mode = 140-3
32+
33+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.jce.provider.1 = sun.security.provider.Sun
34+
35+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.securerandom.provider = OpenJCEPlusFIPS
36+
RestrictedSecurity.Test-Profile-PolicySunset-Expired.securerandom.algorithm = SHA512DRBG
37+
38+
#
39+
# Test-Profile-PolicySunset-ExpireSoon
40+
#
41+
RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.name = Test-Profile-PolicySunset-ExpireSoon
42+
RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.default = true
43+
RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.fips = true
44+
RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.number = Certificate #XXX
45+
RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.policy = https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/
46+
RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.sunsetDate = 2023-09-21
47+
RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.fips.mode = 140-2
48+
49+
RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.jce.provider.1 = sun.security.provider.Sun
50+
51+
RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.securerandom.provider = OpenJCEPlusFIPS
52+
RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.securerandom.algorithm = SHA512DRBG
53+
54+
#
55+
# Test-Profile-PolicySunset-NotExpire
56+
#
57+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.name = Test-Profile-PolicySunset-NotExpire
58+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.default = true
59+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.fips = true
60+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.hash = SHA256:706ce08e67373252aee08c6912f2994e3bbf4691b41ec8258325e37951bf96f3
61+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.number = Certificate #XXX
62+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.policy = https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/
63+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.sunsetDate = 2045-09-21
64+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.fips.mode = 140-3
65+
66+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.jce.provider.1 = sun.security.provider.Sun
67+
68+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.securerandom.provider = OpenJCEPlusFIPS
69+
RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.securerandom.algorithm = SHA512DRBG

0 commit comments

Comments
 (0)