diff --git a/closed/src/java.base/share/classes/openj9/internal/security/RestrictedSecurity.java b/closed/src/java.base/share/classes/openj9/internal/security/RestrictedSecurity.java index 71f3640aa31..3dda2262d07 100644 --- a/closed/src/java.base/share/classes/openj9/internal/security/RestrictedSecurity.java +++ b/closed/src/java.base/share/classes/openj9/internal/security/RestrictedSecurity.java @@ -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; @@ -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; @@ -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) { @@ -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> isJarVerifier = clazz -> "java.util.jar.JarVerifier".equals(clazz.getName()) @@ -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. @@ -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; } /** diff --git a/closed/test/jdk/openj9/internal/security/TestPolicySunset.java b/closed/test/jdk/openj9/internal/security/TestPolicySunset.java new file mode 100644 index 00000000000..e8edc6cf2f3 --- /dev/null +++ b/closed/test/jdk/openj9/internal/security/TestPolicySunset.java @@ -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 . + * + * =========================================================================== + */ + +/* + * @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 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 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); + } + } +} diff --git a/closed/test/jdk/openj9/internal/security/TestProperties.java b/closed/test/jdk/openj9/internal/security/TestProperties.java index 3ee658a889c..13d14448505 100644 --- a/closed/test/jdk/openj9/internal/security/TestProperties.java +++ b/closed/test/jdk/openj9/internal/security/TestProperties.java @@ -120,7 +120,7 @@ private static Stream patternMatches_expectedExitValue1() { // 15 - Test property - policy sunset. Arguments.of("Test-Profile-PolicySunset.Base", System.getProperty("test.src") + "/property-java.security", - "Restricted security policy expired"), + "Use -Dsemeru.restrictedsecurity.ignoresunsetexpiration to allow Java to start while possibly using uncertified cryptography"), // 16 - Test property - policy sunset format. Arguments.of("Test-Profile-PolicySunsetFormat.Base", System.getProperty("test.src") + "/property-java.security", @@ -166,35 +166,40 @@ private static Stream patternMatches_expectedExitValue1() { } private static Stream patternMatches_propertiesList() { - return Stream.of( - // 1 - The profile in propertyListA-java.security extends the profile - // in the main java.security file, which lists 4 providers. - Arguments.of("Test-Profile-Property-List.A", - System.getProperty("test.src") + "/propertyListA-java.security", - "(?s)(?=.*OpenJCEPlusFIPS)(?=.*Sun)(?=.*SunJSSE)(?=.*SunEC)", - 0), - // 2 - The profile in propertyListB-java.security extends the profile - // in propertyListA-java.security, which in turn extends the profile - // in the main java.security file, listing 5 providers. - Arguments.of("Test-Profile-Property-List.B", - System.getProperty("test.src") + "/propertyListA-java.security" + File.pathSeparator - + System.getProperty("test.src") + "/propertyListB-java.security", - "(?s)(?=.*OpenJCEPlusFIPS)(?=.*Sun)(?=.*SunJSSE)(?=.*SunEC)(?=.*SunJCE)", - 0), - // 3 - The profile in propertyListB-java.security extends the profile - // in propertyListA-java.security, which in turn extends the main - // java.security profile, but propertyListB-java.security file is missing. - Arguments.of("Test-Profile-Property-List.B", - System.getProperty("test.src") + "/propertyListB-java.security", - "is not present in the java.security file or any appended files", - 1), - // 4 - The -Djava.security.propertiesList option does not support using - // a leading '=' prefix. - Arguments.of("Test-Profile-Property-List.A", - "=" + System.getProperty("test.src") + "/propertyListA-java.security", - "java.security.propertiesList does not support '=' prefix", - 1) - ); + Stream.Builder tests = Stream.builder(); + + if (isProviderPresent("OpenJCEPlusFIPS")) { + // 1 - The profile in propertyListA-java.security extends the profile + // in the main java.security file, which lists 4 providers. + tests.add(Arguments.of("Test-Profile-Property-List.A", + System.getProperty("test.src") + "/propertyListA-java.security", + "(?s)(?=.*OpenJCEPlusFIPS)(?=.*Sun)(?=.*SunJSSE)(?=.*SunEC)", + 0)); + // 2 - The profile in propertyListB-java.security extends the profile + // in propertyListA-java.security, which in turn extends the profile + // in the main java.security file, listing 5 providers. + tests.add(Arguments.of("Test-Profile-Property-List.B", + System.getProperty("test.src") + "/propertyListA-java.security" + File.pathSeparator + + System.getProperty("test.src") + "/propertyListB-java.security", + "(?s)(?=.*OpenJCEPlusFIPS)(?=.*Sun)(?=.*SunJSSE)(?=.*SunEC)(?=.*SunJCE)", + 0)); + } + + // 3 - The profile in propertyListB-java.security extends the profile + // in propertyListA-java.security, which in turn extends the main + // java.security profile, but propertyListB-java.security file is missing. + tests.add(Arguments.of("Test-Profile-Property-List.B", + System.getProperty("test.src") + "/propertyListB-java.security", + "is not present in the java.security file or any appended files", + 1)); + // 4 - The -Djava.security.propertiesList option does not support using + // a leading '=' prefix. + tests.add(Arguments.of("Test-Profile-Property-List.A", + "=" + System.getProperty("test.src") + "/propertyListA-java.security", + "java.security.propertiesList does not support '=' prefix", + 1)); + + return tests.build(); } @ParameterizedTest @@ -236,6 +241,15 @@ public void shouldContain_propertiesList(String customprofile, String securityPr outputAnalyzer.shouldHaveExitValue(exitValue).shouldMatch(expected); } + private static boolean isProviderPresent(String providerName) { + for (Provider provider : Security.getProviders()) { + if (provider.getName().equalsIgnoreCase(providerName)) { + return true; + } + } + return false; + } + public static void main(String[] args) { // Something to trigger "properties" debug output. try { diff --git a/closed/test/jdk/openj9/internal/security/sunset-java.security b/closed/test/jdk/openj9/internal/security/sunset-java.security new file mode 100644 index 00000000000..eb6dedff53a --- /dev/null +++ b/closed/test/jdk/openj9/internal/security/sunset-java.security @@ -0,0 +1,69 @@ +# =========================================================================== +# (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 . +# =========================================================================== + +# +# Test-Profile-PolicySunset-Expired +# +RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.name = Test-Profile-PolicySunset-Expired +RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.default = true +RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.fips = true +RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.hash = SHA256:365fb3fe2ff69961980d10d16bd76f6efbf07a723b7a8c58a5f6cd234108eaee +RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.number = Certificate #XXX +RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.policy = https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/ +RestrictedSecurity.Test-Profile-PolicySunset-Expired.desc.sunsetDate = 2023-09-21 +RestrictedSecurity.Test-Profile-PolicySunset-Expired.fips.mode = 140-3 + +RestrictedSecurity.Test-Profile-PolicySunset-Expired.jce.provider.1 = sun.security.provider.Sun + +RestrictedSecurity.Test-Profile-PolicySunset-Expired.securerandom.provider = OpenJCEPlusFIPS +RestrictedSecurity.Test-Profile-PolicySunset-Expired.securerandom.algorithm = SHA512DRBG + +# +# Test-Profile-PolicySunset-ExpireSoon +# +RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.name = Test-Profile-PolicySunset-ExpireSoon +RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.default = true +RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.fips = true +RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.number = Certificate #XXX +RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.policy = https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/ +RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.desc.sunsetDate = 2023-09-21 +RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.fips.mode = 140-2 + +RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.jce.provider.1 = sun.security.provider.Sun + +RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.securerandom.provider = OpenJCEPlusFIPS +RestrictedSecurity.Test-Profile-PolicySunset-ExpireSoon.securerandom.algorithm = SHA512DRBG + +# +# Test-Profile-PolicySunset-NotExpire +# +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.name = Test-Profile-PolicySunset-NotExpire +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.default = true +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.fips = true +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.hash = SHA256:706ce08e67373252aee08c6912f2994e3bbf4691b41ec8258325e37951bf96f3 +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.number = Certificate #XXX +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.policy = https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/ +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.desc.sunsetDate = 2045-09-21 +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.fips.mode = 140-3 + +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.jce.provider.1 = sun.security.provider.Sun + +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.securerandom.provider = OpenJCEPlusFIPS +RestrictedSecurity.Test-Profile-PolicySunset-NotExpire.securerandom.algorithm = SHA512DRBG