Skip to content

Commit c8e70fc

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 c8e70fc

File tree

4 files changed

+306
-19
lines changed

4 files changed

+306
-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.println("The requested restricted security profile " + restricts.profileID
642+
+ " expired on " + restricts.descSunsetDate
643+
+ ": certified cryptography use cannot be guaranteed."
644+
+ " Use -Dsemeru.restrictedsecurity.suppresssunsetwarning to stop displaying this message."
645+
+ " The -Dsemeru.restrictedsecurity.ignoresunsetexpiration option has been specified."
646+
+ " WARNING: java will start with the requested restricted security profile but uncertified"
647+
+ " cryptography may be active.");
648+
} else {
649+
printStackTraceAndExit("The requested restricted security profile " + restricts.profileID
650+
+ " expired on " + restricts.descSunsetDate
651+
+ ": java will stop because certified cryptography use cannot be guaranteed."
652+
+ " Use -Dsemeru.restrictedsecurity.suppresssunsetwarning to stop displaying this message."
653+
+ " Use -Dsemeru.restrictedsecurity.ignoresunsetexpiration to allow java to start while"
654+
+ " possibly using uncertified cryptography.");
655+
}
656+
} else if (expireMonths <= 6) { // Check if the SunsetDate will expire within 6 months.
657+
System.err.println("The restricted security profile " + restricts.profileID
658+
+ " will expire on " + restricts.descSunsetDate
659+
+ ", after which java will fail to start if this profile is specified."
660+
+ " The latest Semeru Runtimes release may include an updated security profile.");
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: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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.stream.Stream;
48+
49+
import jdk.test.lib.process.OutputAnalyzer;
50+
import jdk.test.lib.process.ProcessTools;
51+
52+
public class TestPolicySunset {
53+
54+
private static Path updateExpireSoonSunsetFile(String baseFile) {
55+
try {
56+
LocalDate soonDate = LocalDate.now(Clock.systemUTC()).plusMonths(1);
57+
String newDate = soonDate.format(DateTimeFormatter.ISO_DATE);
58+
59+
String content = Files.readString(Paths.get(baseFile), StandardCharsets.UTF_8);
60+
String pattern = "(?m)^(RestrictedSecurity\\.Test-Profile-PolicySunset-ExpireSoon\\.desc\\.sunsetDate)\\s*=.*$";
61+
String updated = content.replaceAll(pattern, "$1 = " + newDate);
62+
63+
Path tmp = Files.createTempFile("sunset-java.security.expireSoon.", ".tmp");
64+
Files.writeString(tmp, updated, StandardCharsets.UTF_8);
65+
return tmp;
66+
} catch (IOException e) {
67+
throw new RuntimeException("Failed to update sunset date for ExpireSoon profile", e);
68+
}
69+
}
70+
71+
private static Stream<Arguments> patternMatches_expectedExitValue0() {
72+
String propertyFile = System.getProperty("test.src") + "/sunset-java.security";
73+
String updatedPropertyFile = updateExpireSoonSunsetFile(propertyFile).toString();
74+
75+
return Stream.of(
76+
// 1 - expired; supress=false; ignore=true
77+
Arguments.of("Test-Profile-PolicySunset-Expired",
78+
propertyFile,
79+
"=false", "=true",
80+
"WARNING: java will start with the requested restricted security profile but uncertified cryptography may be active"),
81+
// 2 - expired; supress=true; ignore=true, no warning
82+
Arguments.of("Test-Profile-PolicySunset-Expired",
83+
propertyFile,
84+
"=true", "=true",
85+
""),
86+
// 3 - expire soon (<=6 months); supress=false
87+
Arguments.of("Test-Profile-PolicySunset-ExpireSoon",
88+
updatedPropertyFile,
89+
"=false", "=false",
90+
"The restricted security profile RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon will expire"),
91+
// 4 - expire soon (<=6 months); supress=true, no warning
92+
Arguments.of("Test-Profile-PolicySunset-ExpireSoon",
93+
updatedPropertyFile,
94+
"=true", "=false",
95+
""),
96+
// 5 - not expire (>6 months); no warning
97+
Arguments.of("Test-Profile-PolicySunset-NotExpire",
98+
propertyFile,
99+
"=false", "=false",
100+
""),
101+
// 6 - expired; property treat empty as true, no warning
102+
Arguments.of("Test-Profile-PolicySunset-Expired",
103+
propertyFile,
104+
"", "" ,
105+
""));
106+
}
107+
108+
private static Stream<Arguments> patternMatches_expectedExitValue1() {
109+
String propertyFile = System.getProperty("test.src") + "/sunset-java.security";
110+
111+
return Stream.of(
112+
// 1 - expired; supress=false; ignore=false
113+
Arguments.of("Test-Profile-PolicySunset-Expired",
114+
propertyFile,
115+
"=false", "=false",
116+
"Use -Dsemeru.restrictedsecurity.ignoresunsetexpiration to allow java to start while possibly using uncertified cryptography"),
117+
// 2 - expired; supress=true; ignore=false, no warning
118+
Arguments.of("Test-Profile-PolicySunset-Expired",
119+
propertyFile,
120+
"=true", "=false",
121+
""));
122+
}
123+
124+
@ParameterizedTest
125+
@MethodSource("patternMatches_expectedExitValue0")
126+
public void shouldContain_expectedExitValue0(String customprofile, String securityPropertyFile, String suppresssunsetwarning, String ignoresunsetexpiration, String expected) throws Exception {
127+
OutputAnalyzer outputAnalyzer = ProcessTools.executeTestJava(
128+
"-Dsemeru.fips=true",
129+
"-Dsemeru.customprofile=" + customprofile,
130+
"-Djava.security.properties=" + securityPropertyFile,
131+
"-Dsemeru.restrictedsecurity.suppresssunsetwarning" + suppresssunsetwarning,
132+
"-Dsemeru.restrictedsecurity.ignoresunsetexpiration" + ignoresunsetexpiration,
133+
"TestPolicySunset"
134+
);
135+
outputAnalyzer.reportDiagnosticSummary();
136+
outputAnalyzer.shouldHaveExitValue(0).shouldMatch(expected);
137+
}
138+
139+
@ParameterizedTest
140+
@MethodSource("patternMatches_expectedExitValue1")
141+
public void shouldContain_expectedExitValue1(String customprofile, String securityPropertyFile, String suppresssunsetwarning, String ignoresunsetexpiration, String expected) throws Exception {
142+
OutputAnalyzer outputAnalyzer = ProcessTools.executeTestJava(
143+
"-Dsemeru.fips=true",
144+
"-Dsemeru.customprofile=" + customprofile,
145+
"-Djava.security.properties=" + securityPropertyFile,
146+
"-Dsemeru.restrictedsecurity.suppresssunsetwarning" + suppresssunsetwarning,
147+
"-Dsemeru.restrictedsecurity.ignoresunsetexpiration" + ignoresunsetexpiration,
148+
"TestPolicySunset"
149+
);
150+
outputAnalyzer.reportDiagnosticSummary();
151+
outputAnalyzer.shouldHaveExitValue(1).shouldMatch(expected);
152+
}
153+
154+
public static void main(String[] args) {
155+
// Something to trigger "properties" debug output.
156+
try {
157+
for (Provider provider : Security.getProviders()) {
158+
System.out.println("Provider Name: " + provider.getName());
159+
System.out.println("Provider Version: " + provider.getVersionStr());
160+
}
161+
} catch (Exception e) {
162+
System.out.println(e);
163+
}
164+
}
165+
}

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)