From c49042f277be36b89d97818e273ee8a6cc05964c Mon Sep 17 00:00:00 2001 From: Kostas Tsiounis Date: Wed, 10 Apr 2024 16:56:52 -0400 Subject: [PATCH] Allow extension of RestrictedSecurity profiles RestrictedSecurity profiles sometimes share a lot of duplicate settings with only minor differences. With these changes the extension, similar to object-orientation, of profiles becomes possible. More specifically, a profile A can extend another a profile B, using RestrictedSecurity..extends = RestrictedSecurity.. This allows profile A to inherit all of profile B's properties. One can add additional properties to profile A, or amend some of the existing ones. That includes overriding, appending or removing from a property (wherever that's applicable). An additional property is introduced. The RestrictedSecurity..desc.hash = : is used to ensure the profile hasn't been unintentionally altered. The profile's properties are hashed using the selected , and the result is compared to the provided through the property. This property is mandatory for base profiles (i.e., profiles that are not extending anything), and optional for the rest. Signed-off-by: Kostas Tsiounis --- .../internal/security/RestrictedSecurity.java | 1390 +++++++++++------ .../classes/sun/security/jca/Providers.java | 9 + .../share/conf/security/java.security | 1 + 3 files changed, 964 insertions(+), 436 deletions(-) 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 5740647e795..61c1ba83ed3 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 @@ -23,7 +23,10 @@ */ package openj9.internal.security; +import java.nio.charset.StandardCharsets; import java.security.AccessController; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.PrivilegedAction; import java.security.Provider.Service; import java.time.LocalDate; @@ -42,6 +45,8 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import sun.security.util.Debug; @@ -71,6 +76,8 @@ public final class RestrictedSecurity { private static String userSecurityID; + private static ProfileParser profileParser; + private static RestrictedSecurityProperties restricts; private static final Set unmodifiableProperties = new HashSet<>(); @@ -164,6 +171,20 @@ private RestrictedSecurity() { super(); } + /** + * Check loaded profiles' hash values. + * + * In order to avoid unintentional changes in profiles and incentivize + * extending profiles, instead of altering them, a digest of the profile + * is calculated and compared to the expected value. + */ + public static void checkHashValues() { + if (profileParser != null) { + profileParser.checkHashValues(); + profileParser = null; + } + } + /** * Check if restricted security mode is enabled. * @@ -290,7 +311,7 @@ public static boolean isProviderAllowed(Class providerClazz) { private static void getProfileID(Properties props) { String potentialProfileID = "RestrictedSecurity." + selectedProfile; - if (selectedProfile.indexOf(".") != -1) { + if (selectedProfile.indexOf('.') != -1) { /* The default profile is used, or the user specified the * full . */ @@ -358,8 +379,8 @@ private static void checkIfKnownProfileSupported() { } } - private static void checkFIPSCompatibility(Properties props) { - boolean isFIPSProfile = Boolean.parseBoolean(props.getProperty(profileID + ".desc.fips")); + private static void checkFIPSCompatibility() { + boolean isFIPSProfile = restricts.descIsFIPS; if (isFIPSProfile) { if (debug != null) { debug.println("RestrictedSecurity profile " + profileID @@ -441,13 +462,9 @@ public static boolean configure(Properties props) { getProfileID(props); checkIfKnownProfileSupported(); - // If user enabled FIPS, check whether chosen profile is applicable. - if (userEnabledFIPS) { - checkFIPSCompatibility(props); - } - // Initialize restricted security properties from java.security file. - restricts = new RestrictedSecurityProperties(profileID, props); + profileParser = new ProfileParser(profileID, props); + restricts = profileParser.getProperties(); // Restricted security properties checks. restrictsCheck(); @@ -536,22 +553,16 @@ private static void setProperties(Properties props) { String jdkPropsName = entry.getKey(); String propsNewValue = entry.getValue(); - String propsOldValue = props.getProperty(jdkPropsName); - if (isNullOrBlank(propsOldValue)) { - propsOldValue = ""; - } - if ((propsNewValue != null) && userEnabledFIPS && !allowSetProperties) { // Add to set of properties set by the active profile. unmodifiableProperties.add(jdkPropsName); } if (!isNullOrBlank(propsNewValue)) { - String values = isNullOrBlank(propsOldValue) ? propsNewValue : (propsOldValue + ", " + propsNewValue); - props.setProperty(jdkPropsName, values); + props.setProperty(jdkPropsName, propsNewValue); if (debug != null) { - debug.println("Added restricted security properties, with property: " + jdkPropsName + " value: " - + values); + debug.println("Added restricted security properties, with property: " + + jdkPropsName + " value: " + propsNewValue); } } } @@ -587,6 +598,11 @@ private static void restrictsCheck() { || isNullOrBlank(restricts.jdkSecureRandomAlgorithm)) { printStackTraceAndExit("Restricted security mode secure random is missing."); } + + // If user enabled FIPS, check whether chosen profile is applicable. + if (userEnabledFIPS) { + checkFIPSCompatibility(); + } } /** @@ -597,12 +613,15 @@ private static void restrictsCheck() { */ private static boolean isPolicySunset(String descSunsetDate) { boolean isSunset = false; - try { - isSunset = LocalDate.parse(descSunsetDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")) - .isBefore(LocalDate.now()); - } catch (DateTimeParseException except) { - printStackTraceAndExit( - "Restricted security policy sunset date is incorrect, the correct format is yyyy-MM-dd."); + // 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()); + } catch (DateTimeParseException except) { + printStackTraceAndExit( + "Restricted security policy sunset date is incorrect, the correct format is yyyy-MM-dd."); + } } if (debug != null) { @@ -630,34 +649,66 @@ private static void printStackTraceAndExit(String message) { printStackTraceAndExit(new RuntimeException(message)); } + /** + * Check if the input string is asterisk (*). + * + * @param string input string for checking + * @return true if the input string is asterisk + */ + private static boolean isAsterisk(String string) { + return "*".equals(string); + } + + /** + * Get the provider name defined in provider construction method. + * + * @param providerName provider name or provider with packages + * @return provider name defined in provider construction method + */ + private static String getProvidersSimpleName(String providerName) { + if (providerName.equals("com.sun.security.sasl.Provider")) { + // The main class for the SunSASL provider is com.sun.security.sasl.Provider. + return "SunSASL"; + } else { + // Remove the provider's class package names if present. + int pos = providerName.lastIndexOf('.'); + if (pos >= 0) { + providerName = providerName.substring(pos + 1); + } + // Provider without package names. + return providerName; + } + } + /** * This class is used to save and operate on restricted security * properties which are loaded from the java.security file. */ private static final class RestrictedSecurityProperties { + private final String profileID; - private String descName; - private boolean descIsDefault; - private boolean descIsFIPS; - private String descNumber; - private String descPolicy; - private String descSunsetDate; + private final String descName; + private final boolean descIsDefault; + private final boolean descIsFIPS; + private final String descNumber; + private final String descPolicy; + private final String descSunsetDate; // Security properties. - private String jdkTlsDisabledNamedCurves; - private String jdkTlsDisabledAlgorithms; - private String jdkTlsEphemeralDHKeySize; - private String jdkTlsLegacyAlgorithms; - private String jdkCertpathDisabledAlgorithms; - private String jdkSecurityLegacyAlgorithms; - private String keyStoreType; - private String keyStore; + private final String jdkTlsDisabledNamedCurves; + private final String jdkTlsDisabledAlgorithms; + private final String jdkTlsEphemeralDHKeySize; + private final String jdkTlsLegacyAlgorithms; + private final String jdkCertpathDisabledAlgorithms; + private final String jdkSecurityLegacyAlgorithms; + private final String keyStoreType; + private final String keyStore; // For SecureRandom. - String jdkSecureRandomProvider; - String jdkSecureRandomAlgorithm; + final String jdkSecureRandomProvider; + final String jdkSecureRandomAlgorithm; - String jdkFipsMode; + final String jdkFipsMode; // Provider with argument (provider name + optional argument). private final List providers; @@ -666,265 +717,49 @@ private static final class RestrictedSecurityProperties { // The map is keyed by provider name. private final Map providerConstraints; - private final String profileID; - - // The java.security properties. - private final Properties securityProps; - - /** - * - * @param id the restricted security custom profile ID - * @param props the java.security properties - * @param trace the user security trace - * @param audit the user security audit - * @param help the user security help - */ - private RestrictedSecurityProperties(String id, Properties props) { - Objects.requireNonNull(props); - - profileID = id; - securityProps = props; - - providers = new ArrayList<>(); - providersSimpleName = new ArrayList<>(); - providerConstraints = new HashMap<>(); - - // Initialize the properties. - init(); - } - - /** - * Initialize restricted security properties. - */ - private void init() { - if (debug != null) { - debug.println("Initializing restricted security mode."); - } - - try { - // Load restricted security providers from java.security properties. - initProviders(); - // Load restricted security properties from java.security properties. - initProperties(); - // Load restricted security provider constraints from java.security properties. - initConstraints(); - } catch (Exception e) { - if (debug != null) { - debug.println("Unable to initialize restricted security mode."); - } - printStackTraceAndExit(e); - } + private RestrictedSecurityProperties(String profileID, ProfileParser parser) { + this.profileID = profileID; + + this.descName = parser.getProperty("descName"); + this.descIsDefault = parser.descIsDefault; + this.descIsFIPS = parser.descIsFIPS; + this.descNumber = parser.getProperty("descNumber"); + + this.descPolicy = parser.getProperty("descPolicy"); + this.descSunsetDate = parser.getProperty("descSunsetDate"); + + // Security properties. + this.jdkTlsDisabledNamedCurves = parser.getProperty("jdkTlsDisabledNamedCurves"); + this.jdkTlsDisabledAlgorithms = parser.getProperty("jdkTlsDisabledAlgorithms"); + this.jdkTlsEphemeralDHKeySize = parser.getProperty("jdkTlsEphemeralDHKeySize"); + this.jdkTlsLegacyAlgorithms = parser.getProperty("jdkTlsLegacyAlgorithms"); + this.jdkCertpathDisabledAlgorithms = parser.getProperty("jdkCertpathDisabledAlgorithms"); + this.jdkSecurityLegacyAlgorithms = parser.getProperty("jdkSecurityLegacyAlgorithms"); + this.keyStoreType = parser.getProperty("keyStoreType"); + this.keyStore = parser.getProperty("keyStore"); + + // For SecureRandom. + this.jdkSecureRandomProvider = parser.getProperty("jdkSecureRandomProvider"); + this.jdkSecureRandomAlgorithm = parser.getProperty("jdkSecureRandomAlgorithm"); + + this.jdkFipsMode = parser.getProperty("jdkFipsMode"); + + this.providers = new ArrayList<>(parser.providers); + this.providersSimpleName = new ArrayList<>(parser.providersSimpleName); + this.providerConstraints = parser.providerConstraints + .entrySet() + .stream() + .collect(Collectors.toMap( + e -> e.getKey(), + e -> e.getValue().toArray(new Constraint[0]) + )); if (debug != null) { - debug.println("Initialization of restricted security mode completed."); - - // Print all available restricted security profiles. - listAvailableProfiles(); - // Print information of utilized security profile. listUsedProfile(); } } - /** - * Load restricted security provider. - */ - private void initProviders() { - if (debug != null) { - debug.println("\tLoading providers of restricted security profile."); - } - - for (int pNum = 1;; ++pNum) { - String providerInfo = securityProps - .getProperty(profileID + ".jce.provider." + pNum); - - if ((providerInfo == null) || providerInfo.trim().isEmpty()) { - break; - } - - if (!areBracketsBalanced(providerInfo)) { - printStackTraceAndExit("Provider format is incorrect: " + providerInfo); - } - - int pos = providerInfo.indexOf('['); - String providerName = (pos < 0) ? providerInfo.trim() : providerInfo.substring(0, pos).trim(); - // Provider with argument (provider name + optional argument). - providers.add(pNum - 1, providerName); - - // Remove the provider's optional arguments if there are. - pos = providerName.indexOf(' '); - if (pos >= 0) { - providerName = providerName.substring(0, pos); - } - providerName = providerName.trim(); - - // Remove argument, e.g. -NSS-FIPS, if present. - pos = providerName.indexOf('-'); - if (pos >= 0) { - providerName = providerName.substring(0, pos); - } - - // Provider name defined in provider construction method. - providerName = getProvidersSimpleName(providerName); - providersSimpleName.add(pNum - 1, providerName); - } - - if (providers.isEmpty()) { - printStackTraceAndExit( - "No providers are specified as part of the Restricted Security profile."); - } - - if (debug != null) { - debug.println("\tProviders of restricted security profile successfully loaded."); - } - } - - /** - * Load restricted security properties. - */ - private void initProperties() { - if (debug != null) { - debug.println("\tLoading properties of restricted security profile."); - } - - descName = parseProperty(securityProps.getProperty(profileID + ".desc.name")); - descIsDefault = Boolean.parseBoolean(parseProperty(securityProps.getProperty(profileID + ".desc.default"))); - descIsFIPS = Boolean.parseBoolean(parseProperty(securityProps.getProperty(profileID + ".desc.fips"))); - descNumber = parseProperty(securityProps.getProperty(profileID + ".desc.number")); - descPolicy = parseProperty(securityProps.getProperty(profileID + ".desc.policy")); - descSunsetDate = parseProperty(securityProps.getProperty(profileID + ".desc.sunsetDate")); - - jdkTlsDisabledNamedCurves = parseProperty( - securityProps.getProperty(profileID + ".tls.disabledNamedCurves")); - jdkTlsDisabledAlgorithms = parseProperty( - securityProps.getProperty(profileID + ".tls.disabledAlgorithms")); - jdkTlsEphemeralDHKeySize = parseProperty( - securityProps.getProperty(profileID + ".tls.ephemeralDHKeySize")); - jdkTlsLegacyAlgorithms = parseProperty( - securityProps.getProperty(profileID + ".tls.legacyAlgorithms")); - jdkCertpathDisabledAlgorithms = parseProperty( - securityProps.getProperty(profileID + ".jce.certpath.disabledAlgorithms")); - jdkSecurityLegacyAlgorithms = parseProperty( - securityProps.getProperty(profileID + ".jce.legacyAlgorithms")); - keyStoreType = parseProperty( - securityProps.getProperty(profileID + ".keystore.type")); - keyStore = parseProperty( - securityProps.getProperty(profileID + ".javax.net.ssl.keyStore")); - - jdkSecureRandomProvider = parseProperty( - securityProps.getProperty(profileID + ".securerandom.provider")); - jdkSecureRandomAlgorithm = parseProperty( - securityProps.getProperty(profileID + ".securerandom.algorithm")); - jdkFipsMode = parseProperty( - securityProps.getProperty(profileID + ".fips.mode")); - - if (debug != null) { - debug.println("\tProperties of restricted security profile successfully loaded."); - } - } - - /** - * Load security constraints with type, algorithm, attributes. - * - * Example: - * RestrictedSecurity1.jce.provider.1 = SUN [{CertPathBuilder, PKIX, *}, - * {Policy, JavaPolicy, *}, {CertPathValidator, *, *}]. - */ - private void initConstraints() { - if (debug != null) { - debug.println("\tLoading constraints of restricted security profile."); - } - - for (int pNum = 1; pNum <= providersSimpleName.size(); pNum++) { - String providerName = providersSimpleName.get(pNum - 1); - String providerInfo = securityProps - .getProperty(profileID + ".jce.provider." + pNum); - - if (debug != null) { - debug.println("\t\tLoading constraints for security provider: " + providerName); - } - - // Check if the provider has constraints. - if (providerInfo.indexOf('[') < 0) { - if (debug != null) { - debug.println("\t\t\tNo constraints for security provider: " + providerName); - } - providerConstraints.put(providerName, new Constraint[0]); - continue; - } - - // Remove the whitespaces in the format separator if there are. - providerInfo = providerInfo.trim() - .replaceAll("\\[\\s+\\{", "[{") - .replaceAll("\\}\\s+\\]", "}]") - .replaceAll("\\}\\s*,\\s*\\{", "},{"); - - int startIndex = providerInfo.lastIndexOf("[{"); - int endIndex = providerInfo.indexOf("}]"); - - // Provider with constraints. - if ((startIndex > 0) && (endIndex > startIndex)) { - String[] constrArray = providerInfo - .substring(startIndex + 2, endIndex).split("\\},\\{"); - - if (constrArray.length <= 0) { - printStackTraceAndExit("Constraint format is incorrect: " + providerInfo); - } - - // Constraint object array. - // For each constraint type, algorithm and attributes. - Constraint[] constraints = new Constraint[constrArray.length]; - - int cNum = 0; - for (String constr : constrArray) { - String[] input = constr.split(","); - - // Each constraint must includes 3 fields(type, algorithm, attributes). - if (input.length != 3) { - printStackTraceAndExit("Constraint format is incorrect: " + providerInfo); - } - - String inType = input[0].trim(); - String inAlgorithm = input[1].trim(); - String inAttributes = input[2].trim(); - - // Each attribute must includes 2 fields (key and value) or *. - if (!isAsterisk(inAttributes)) { - String[] attributeArray = inAttributes.split(":"); - for (String attribute : attributeArray) { - String[] in = attribute.split("=", 2); - if (in.length != 2) { - printStackTraceAndExit( - "Constraint attributes format is incorrect: " + providerInfo); - } - } - } - - Constraint constraint = new Constraint(inType, inAlgorithm, inAttributes); - - if (debug != null) { - debug.println("\t\t\tConstraint specified for security provider: " + providerName); - debug.println("\t\t\t\twith type: " + inType); - debug.println("\t\t\t\tfor algorithm: " + inAlgorithm); - debug.println("\t\t\t\twith attributes: " + inAttributes); - } - constraints[cNum] = constraint; - cNum++; - } - providerConstraints.put(providerName, constraints); - if (debug != null) { - debug.println("\t\tSuccessfully loaded constraints for security provider: " + providerName); - } - } else { - printStackTraceAndExit("Constraint format is incorrect: " + providerInfo); - } - } - - if (debug != null) { - debug.println("\tAll constraints of restricted security profile successfully loaded."); - } - } - /** * Check if the Service is allowed in restricted security mode. * @@ -934,6 +769,10 @@ private void initConstraints() { boolean isRestrictedServiceAllowed(Service service) { String providerName = service.getProvider().getName(); + if (debug != null) { + debug.println("Checking service " + service.toString() + " offered by provider " + providerName + "."); + } + // Provider with argument, remove argument. // e.g. SunPKCS11-NSS-FIPS, remove argument -NSS-FIPS. int pos = providerName.indexOf('-'); @@ -943,9 +782,16 @@ boolean isRestrictedServiceAllowed(Service service) { if (constraints == null) { // Disallow unknown providers. + if (debug != null) { + debug.println("Security constraints check." + + " Disallow unknown provider: " + providerName); + } return false; } else if (constraints.length == 0) { // Allow this provider with no constraints. + if (debug != null) { + debug.println("No constraints for provider " + providerName + "."); + } return true; } @@ -953,27 +799,42 @@ boolean isRestrictedServiceAllowed(Service service) { String type = service.getType(); String algorithm = service.getAlgorithm(); + if (debug != null) { + debug.println("Security constraints check of provider."); + } for (Constraint constraint : constraints) { String cType = constraint.type; String cAlgorithm = constraint.algorithm; String cAttribute = constraint.attributes; + if (debug != null) { + debug.println("Checking provider constraint:" + + "\n\tService type: " + cType + + "\n\tAlgorithm: " + cAlgorithm + + "\n\tAttributes: " + cAttribute); + } if (!isAsterisk(cType) && !type.equals(cType)) { // The constraint doesn't apply to the service type. + if (debug != null) { + debug.println("The constraint doesn't apply to the service type."); + } continue; } if (!isAsterisk(cAlgorithm) && !algorithm.equalsIgnoreCase(cAlgorithm)) { - // The constraint doesn't apply to the service algorith. + // The constraint doesn't apply to the service algorithm. + if (debug != null) { + debug.println("The constraint doesn't apply to the service algorithm."); + } continue; } // For type and algorithm match, and attribute is *. if (isAsterisk(cAttribute)) { if (debug != null) { - debug.println("Security constraints check." - + " Service type: " + type - + " Algorithm: " + algorithm - + " is allowed in provider " + providerName); + debug.println("The following service:" + + "\n\tService type: " + type + + "\n\tAlgorithm: " + algorithm + + "\nis allowed in provider: " + providerName); } return true; } @@ -989,36 +850,47 @@ boolean isRestrictedServiceAllowed(Service service) { String cName = input[0].trim(); String cValue = input[1].trim(); String sValue = service.getAttribute(cName); + if (debug != null) { + debug.println("Checking specific attribute with:" + + "\n\tName: " + cName + + "\n\tValue: " + cValue + + "\nagainst the service attribute value: " + sValue); + } if ((sValue == null) || !cValue.equalsIgnoreCase(sValue)) { // If any attribute doesn't match, return service is not allowed. if (debug != null) { - debug.println( - "Security constraints check." - + " Service type: " + type - + " Algorithm: " + algorithm - + " Attribute: " + cAttribute - + " is NOT allowed in provider: " + providerName); + debug.println("Attributes don't match!"); + debug.println("The following service:" + + "\n\tService type: " + type + + "\n\tAlgorithm: " + algorithm + + "\n\tAttribute: " + cAttribute + + "\nis NOT allowed in provider: " + providerName); } return false; } + if (debug != null) { + debug.println("Attributes match!"); + } } if (debug != null) { - debug.println( - "Security constraints check." - + " Service type: " + type - + " Algorithm: " + algorithm - + " Attribute: " + cAttribute - + " is allowed in provider: " + providerName); + debug.println("All attributes matched!"); + debug.println("The following service:" + + "\n\tService type: " + type + + "\n\tAlgorithm: " + algorithm + + "\n\tAttribute: " + cAttribute + + "\nis allowed in provider: " + providerName); } return true; } + + // No match for any constraint, return NOT allowed. if (debug != null) { - debug.println("Security constraints check." - + " Service type: " + type - + " Algorithm: " + algorithm - + " is NOT allowed in provider " + providerName); + debug.println("Could not find a constraint to match."); + debug.println("The following service:" + + "\n\tService type: " + type + + "\n\tAlgorithm: " + algorithm + + "\nis NOT allowed in provider: " + providerName); } - // No match for any constraint, return NOT allowed. return false; } @@ -1033,7 +905,7 @@ boolean isRestrictedProviderAllowed(String providerName) { debug.println("Checking the provider " + providerName + " in restricted security mode."); } - // Remove argument, e.g. -NSS-FIPS, if there is. + // Remove argument, e.g. -NSS-FIPS, if present. int pos = providerName.indexOf('-'); if (pos >= 0) { providerName = providerName.substring(0, pos); @@ -1066,23 +938,602 @@ boolean isRestrictedProviderAllowed(String providerName) { } /** - * Get the provider name defined in provider construction method. + * List the RestrictedSecurity profile currently used. + */ + private void listUsedProfile() { + System.out.println(); + System.out.println("Utilized Restricted Security Profile Info:"); + System.out.println("=========================================="); + System.out.println("The Restricted Security profile used is: " + profileID); + System.out.println(); + System.out.println(profileID + " Profile Info:"); + System.out.println("=========================================="); + printProperty(profileID + ".desc.name: ", descName); + printProperty(profileID + ".desc.default: ", "" + descIsDefault); + printProperty(profileID + ".desc.fips: ", "" + descIsFIPS); + printProperty(profileID + ".fips.mode: ", jdkFipsMode); + printProperty(profileID + ".desc.number: ", descNumber); + printProperty(profileID + ".desc.policy: ", descPolicy); + printProperty(profileID + ".desc.sunsetDate: ", descSunsetDate); + System.out.println(); + + // List providers. + System.out.println(profileID + " Profile Providers:"); + System.out.println("==============================================="); + for (int providerPosition = 0; providerPosition < providers.size(); providerPosition++) { + printProperty(profileID + ".jce.provider." + (providerPosition + 1) + ": ", + providers.get(providerPosition)); + String providerSimpleName = providersSimpleName.get(providerPosition); + for (Constraint providerConstraint : providerConstraints.get(providerSimpleName)) { + System.out.println("\t" + providerConstraint.toString()); + } + } + System.out.println(); + + // List profile restrictions. + System.out.println(profileID + " Profile Restrictions:"); + System.out.println("=================================================="); + printProperty(profileID + ".tls.disabledNamedCurves: ", jdkTlsDisabledNamedCurves); + printProperty(profileID + ".tls.disabledAlgorithms: ", jdkTlsDisabledAlgorithms); + printProperty(profileID + ".tls.ephemeralDHKeySize: ", jdkTlsEphemeralDHKeySize); + printProperty(profileID + ".tls.legacyAlgorithms: ", jdkTlsLegacyAlgorithms); + printProperty(profileID + ".jce.certpath.disabledAlgorithms: ", jdkCertpathDisabledAlgorithms); + printProperty(profileID + ".jce.legacyAlgorithms: ", jdkSecurityLegacyAlgorithms); + System.out.println(); + + printProperty(profileID + ".keystore.type: ", keyStoreType); + printProperty(profileID + ".javax.net.ssl.keyStore: ", keyStore); + printProperty(profileID + ".securerandom.provider: ", jdkSecureRandomProvider); + printProperty(profileID + ".securerandom.algorithm: ", jdkSecureRandomAlgorithm); + System.out.println(); + } + + private static void printProperty(String name, String value) { + if (value != null) { + String valueToPrint = (value.isEmpty()) ? "EMPTY" : value; + System.out.println(name + valueToPrint); + } else if (debug != null) { + debug.println("Nothing to print. Value of property " + name + " is null."); + } + } + } + + private static final class ProfileParser { + // Properties specified through the profile. + private final Map profileProperties; + private boolean descIsDefault; + private boolean descIsFIPS; + + // Provider with argument (provider name + optional argument). + private final List providers; + // Provider without argument. + private final List providersSimpleName; + // The map is keyed by provider name. + private final Map> providerConstraints; + + private final String profileID; + + private final Map profilesHashes; + private final Map> profilesInfo; + + private final Set parsedProfiles; + + // The java.security properties. + private final Properties securityProps; + + /** * - * @param providerName provider name or provider with packages - * @return provider name defined in provider construction method + * @param id the restricted security custom profile ID + * @param props the java.security properties + */ + private ProfileParser(String id, Properties props) { + Objects.requireNonNull(props); + + profileID = id; + securityProps = props; + + profileProperties = new HashMap<>(); + + providers = new ArrayList<>(); + providersSimpleName = new ArrayList<>(); + providerConstraints = new HashMap<>(); + + profilesHashes = new HashMap<>(); + profilesInfo = new HashMap<>(); + + parsedProfiles = new HashSet<>(); + + // Initialize the properties. + init(profileID); + } + + private RestrictedSecurityProperties getProperties() { + return new RestrictedSecurityProperties(this.profileID, this); + } + + private boolean isFIPS1402Profile(String profileID) { + return "140-2".equals(securityProps.getProperty(profileID + ".fips.mode")); + } + + /** + * Initialize restricted security properties. */ - private static String getProvidersSimpleName(String providerName) { - if (providerName.equals("com.sun.security.sasl.Provider")) { - // The main class for the SunSASL provider is com.sun.security.sasl.Provider. - return "SunSASL"; + private void init(String profileID) { + if (debug != null) { + debug.println("Initializing restricted security properties for '" + profileID + "'."); + } + + if (!parsedProfiles.add(profileID)) { + printStackTraceAndExit(profileID + " has already been parsed. Potential infinite recursion."); + } + + String potentialExtendsProfileID = parseProperty(securityProps.getProperty(profileID + ".extends")); + if (potentialExtendsProfileID != null) { // If profile extends another profile. + if (debug != null) { + debug.println("\t'" + profileID + "' extends '" + potentialExtendsProfileID + "'."); + } + + // Check if extended profile exists. + String extendsProfileID = null; + if (potentialExtendsProfileID.indexOf('.') != potentialExtendsProfileID.lastIndexOf('.')) { + // Extended profile id has at least 2 dots (meaning it's a full profile id). + int prefixLength = potentialExtendsProfileID.length(); + for (Object keyObject : securityProps.keySet()) { + if (keyObject instanceof String key && key.startsWith(potentialExtendsProfileID)) { + String suffix = key.substring(prefixLength); + if (suffix.startsWith(".desc") + || suffix.startsWith(".fips") + || suffix.startsWith(".javax") + || suffix.startsWith(".jce") + || suffix.startsWith(".securerandom") + || suffix.startsWith(".tls") + ) { + /* If even one security property is found for this profile id, + * then it is a valid one and there is no need to check more + * properties. + */ + extendsProfileID = potentialExtendsProfileID; + break; + } + } + } + if (extendsProfileID == null) { + printStackTraceAndExit(potentialExtendsProfileID + " that is supposed to extend '" + + profileID + "' is not present in the java.security file or any appended files."); + } + } else { + printStackTraceAndExit(potentialExtendsProfileID + " that is supposed to extend '" + + profileID + "' is not a full profile name."); + } + + // Recursively call init() on extended profile. + init(extendsProfileID); + + // Perform update based on current profile. + update(profileID); + } else { + try { + List allInfo = new ArrayList<>(); + // Load restricted security providers from java.security properties. + initProviders(profileID, allInfo); + // Load restricted security properties from java.security properties. + loadProperties(profileID, allInfo); + + String hashProperty = profileID + ".desc.hash"; + String hashValue = securityProps.getProperty(hashProperty); + if (hashValue != null) { + // Save info to be hashed and expected result to be checked later. + profilesHashes.put(profileID, hashValue); + profilesInfo.put(profileID, allInfo); + } else if (!isFIPS1402Profile(profileID)) { + // A hash is mandatory, but not for older 140-2 profiles. + printStackTraceAndExit(profileID + " is a base profile, so a hash value is mandatory."); + } + } catch (Exception e) { + if (debug != null) { + debug.println("Unable to initialize restricted security mode."); + } + printStackTraceAndExit(e); + } + } + + if (debug != null) { + debug.println("Initialization of restricted security properties for '" + profileID + "' completed."); + } + } + + /** + * Initialize restricted security properties. + */ + private void update(String profileExtensionId) { + try { + List allInfo = new ArrayList<>(); + // Load restricted security providers from java.security properties. + updateProviders(profileExtensionId, allInfo); + // Load restricted security properties from java.security properties. + loadProperties(profileExtensionId, allInfo); + + String hashProperty = profileExtensionId + ".desc.hash"; + String hashValue = securityProps.getProperty(hashProperty); + + // Hash value is optional in extension profiles. + if (hashValue != null) { + // Save info to be hashed and expected result to be checked later. + profilesHashes.put(profileID, hashValue); + profilesInfo.put(profileID, allInfo); + } + } catch (Exception e) { + if (debug != null) { + debug.println("Unable to update restricted security properties for '" + profileExtensionId + "'."); + } + printStackTraceAndExit(e); + } + } + + private void parseProvider(String providerInfo, int providerPos, boolean update) { + if (debug != null) { + debug.println("\t\tLoading provider in position " + providerPos); + } + + checkProviderFormat(providerInfo, update); + + int pos = providerInfo.indexOf('['); + String providerName = (pos < 0) ? providerInfo.trim() : providerInfo.substring(0, pos).trim(); + // Provider with argument (provider name + optional argument). + if (update) { + providers.set(providerPos - 1, providerName); + } else { + providers.add(providerPos - 1, providerName); + } + + // Remove the provider's optional arguments if there are. + pos = providerName.indexOf(' '); + if (pos >= 0) { + providerName = providerName.substring(0, pos); + } + providerName = providerName.trim(); + + // Remove argument, e.g. -NSS-FIPS, if present. + pos = providerName.indexOf('-'); + if (pos >= 0) { + providerName = providerName.substring(0, pos); + } + + // Provider name defined in provider construction method. + providerName = getProvidersSimpleName(providerName); + boolean providerChanged = false; + if (update) { + String previousProviderName = providersSimpleName.get(providerPos - 1); + providerChanged = !previousProviderName.equals(providerName); + providersSimpleName.set(providerPos - 1, providerName); } else { - // Remove the provider's class package names if present. - int pos = providerName.lastIndexOf('.'); - if (pos >= 0) { - providerName = providerName.substring(pos + 1); + providersSimpleName.add(providerPos - 1, providerName); + } + + if (debug != null) { + debug.println("\t\tLoaded provider in position " + providerPos + " named: " + providerName); + } + + // Set the provided constraints for this provider. + setConstraints(providerName, providerInfo, providerChanged); + } + + private void removeProvider(String profileExtensionId, int providerPos) { + if (debug != null) { + debug.println("\t\tRemoving provider in position " + providerPos); + } + + int numOfExistingProviders = providersSimpleName.size(); + + // If this is the last provider, remove from all lists. + if (providerPos == numOfExistingProviders) { + if (debug != null) { + debug.println("\t\t\tLast provider. Only one to be removed."); + } + String providerRemoved = providersSimpleName.remove(providerPos - 1); + providers.remove(providerPos - 1); + providerConstraints.remove(providerRemoved); + + if (debug != null) { + debug.println("\t\tProvider " + providerRemoved + " removed."); + } + return; + } + + // If there's more, check that all of the subsequent ones are set to be removed. + for (int i = numOfExistingProviders; i >= providerPos; i--) { + if (debug != null) { + debug.println("\t\t\tNot the last provider. More to be removed."); + } + + String providerInfo = securityProps.getProperty(profileExtensionId + ".jce.provider." + i); + if ((providerInfo == null) || !providerInfo.isBlank()) { + printStackTraceAndExit( + "Cannot specify an empty provider in position " + + providerPos + " when non-empty ones are specified after it."); + } + + // Remove all of the providers that are set to empty. + String providerRemoved = providersSimpleName.remove(i - 1); + providers.remove(i - 1); + providerConstraints.remove(providerRemoved); + + if (debug != null) { + debug.println("\t\tProvider " + providerRemoved + " removed."); + } + } + } + + /** + * Load restricted security provider. + */ + private void initProviders(String profileID, List allInfo) { + if (debug != null) { + debug.println("\tLoading providers of restricted security profile."); + } + + for (int pNum = 1;; ++pNum) { + String property = profileID + ".jce.provider." + pNum; + String providerInfo = securityProps.getProperty(property); + + if (providerInfo == null) { + break; + } + + if (providerInfo.isBlank()) { + printStackTraceAndExit( + "Cannot specify an empty provider in position " + + pNum + ". Nothing specified before."); + } + + allInfo.add(property + "=" + providerInfo); + + parseProvider(providerInfo, pNum, false); + } + + if (providers.isEmpty()) { + printStackTraceAndExit( + "No providers are specified as part of the Restricted Security profile."); + } + + if (debug != null) { + debug.println("\tProviders of restricted security profile successfully loaded."); + } + } + + private void updateProviders(String profileExtensionId, List allInfo) { + boolean removedProvider = false; + int numOfExistingProviders = providersSimpleName.size(); + // Deal with update of existing providers. + for (int i = 1; i <= numOfExistingProviders; i++) { + String property = profileExtensionId + ".jce.provider." + i; + String providerInfo = securityProps.getProperty(property); + if (providerInfo != null) { + allInfo.add(property + "=" + providerInfo); + if (!providerInfo.isBlank()) { + // Update the specific provider. + parseProvider(providerInfo, i, true); + } else { + // Remove provider(s) after checking. + removeProvider(profileExtensionId, i); + removedProvider = true; + break; + } + } + } + + // Deal with additional providers added. + for (int i = numOfExistingProviders + 1;; i++) { + String property = profileExtensionId + ".jce.provider." + i; + String providerInfo = securityProps.getProperty(property); + + if (providerInfo == null) { + break; + } + + if (providerInfo.isBlank()) { + printStackTraceAndExit( + "Cannot specify an empty provider in position " + + i + ". Nothing specified before."); + } + + if (removedProvider) { + printStackTraceAndExit( + "Cannot add a provider in position " + i + + " after removing the ones in previous positions."); + } + + allInfo.add(property + "=" + providerInfo); + + parseProvider(providerInfo, i, false); + } + } + + /** + * Load restricted security properties. + */ + private void loadProperties(String profileID, List allInfo) { + if (debug != null) { + debug.println("\tLoading properties of restricted security profile."); + } + + setProperty("descName", profileID + ".desc.name", allInfo); + if (setProperty("descIsDefaultString", profileID + ".desc.default", allInfo)) { + descIsDefault = Boolean.parseBoolean(profileProperties.get("descIsDefaultString")); + } + if (setProperty("descIsFIPSString", profileID + ".desc.fips", allInfo)) { + descIsFIPS = Boolean.parseBoolean(profileProperties.get("descIsFIPSString")); + } + setProperty("descNumber", profileID + ".desc.number", allInfo); + setProperty("descPolicy", profileID + ".desc.policy", allInfo); + setProperty("descSunsetDate", profileID + ".desc.sunsetDate", allInfo); + + setProperty("jdkTlsDisabledNamedCurves", + profileID + ".tls.disabledNamedCurves", allInfo); + setProperty("jdkTlsDisabledAlgorithms", + profileID + ".tls.disabledAlgorithms", allInfo); + setProperty("jdkTlsEphemeralDHKeySize", + profileID + ".tls.ephemeralDHKeySize", allInfo); + setProperty("jdkTlsLegacyAlgorithms", + profileID + ".tls.legacyAlgorithms", allInfo); + setProperty("jdkCertpathDisabledAlgorithms", + profileID + ".jce.certpath.disabledAlgorithms", allInfo); + setProperty("jdkSecurityLegacyAlgorithms", + profileID + ".jce.legacyAlgorithms", allInfo); + setProperty("keyStoreType", + profileID + ".keystore.type", allInfo); + setProperty("keyStore", + profileID + ".javax.net.ssl.keyStore", allInfo); + + setProperty("jdkSecureRandomProvider", + profileID + ".securerandom.provider", allInfo); + setProperty("jdkSecureRandomAlgorithm", + profileID + ".securerandom.algorithm", allInfo); + setProperty("jdkFipsMode", + profileID + ".fips.mode", allInfo); + + if (debug != null) { + debug.println("\tProperties of restricted security profile successfully loaded."); + } + } + + private void setConstraints(String providerName, String providerInfo, boolean providerChanged) { + if (debug != null) { + debug.println("\t\tLoading constraints for security provider: " + providerName); + } + + List constraints = new ArrayList<>(); + + providerInfo = providerInfo.replaceAll("\\s+", ""); + + // Check whether constraints are specified for this provider. + Pattern p = Pattern.compile("\\[.+\\]"); + Matcher m = p.matcher(providerInfo); + if (!m.find()) { + if (debug != null) { + debug.println("\t\t\tNo constraints for security provider: " + providerName); + } + providerConstraints.put(providerName, constraints); + return; + } + + // Check whether constraints are properly specified. + final String typeRE = "\\w+"; + final String algoRE = "[A-Za-z0-9./_-]+"; + final String attrRE = "[A-Za-z0-9=*|.:]+"; + final String consRE = "\\{(" + typeRE + "),(" + algoRE + "),(" + attrRE + ")\\}"; + p = Pattern.compile( + "\\[" + + "([+-]?)" // option to append or remove + + consRE // at least one constraint + + "(," + consRE + ")*" // more constraints [optional] + + "\\]"); + m = p.matcher(providerInfo); + + if (!m.find()) { + printStackTraceAndExit("Incorrect constraint definition for provider " + providerName); + } + + String action = m.group(1); + + // Parse all provided constraints. + p = Pattern.compile(consRE); + m = p.matcher(providerInfo); + + while (m.find()) { + String inType = m.group(1); + String inAlgorithm = m.group(2); + String inAttributes = m.group(3); + + // Each attribute must includes 2 fields (key and value) or *. + if (!isAsterisk(inAttributes)) { + String[] attributeArray = inAttributes.split(":"); + for (String attribute : attributeArray) { + String[] in = attribute.split("=", 2); + if (in.length != 2) { + printStackTraceAndExit( + "Constraint attributes format is incorrect: " + providerInfo); + } + } + } + Constraint constraint = new Constraint(inType, inAlgorithm, inAttributes); + constraints.add(constraint); + } + + // Differeriante between add, remove and override. + if (isNullOrBlank(action)) { + providerConstraints.put(providerName, constraints); + } else { + if (providerChanged) { + printStackTraceAndExit( + "Cannot append or remove constraints since the provider " + providerName + + " wasn't in this position in the profile extended."); + } + List existingConstraints = providerConstraints.get(providerName); + if (existingConstraints == null) { + existingConstraints = new ArrayList<>(); + providerConstraints.put(providerName, existingConstraints); + } + if (action.equals("+")) { // Appending constraints. + existingConstraints.addAll(constraints); + } else { // Removing constraints. + for (Constraint toRemove : constraints) { + if (!existingConstraints.remove(toRemove)) { + printStackTraceAndExit( + "Constraint " + toRemove + "is not part of existing constraints."); + } + } + } + } + + if (debug != null) { + debug.println("\t\t\tSuccessfully loaded constraints for security provider: " + providerName); + } + } + + private void checkHashValues() { + for (Map.Entry entry : profilesHashes.entrySet()) { + String profileID = entry.getKey(); + String hashValue = entry.getValue(); + List allInfo = profilesInfo.get(profileID); + + if (debug != null) { + debug.println("Calculating hash for '" + profileID + "'."); + } + String[] hashInfo = hashValue.split(":"); + if (hashInfo.length != 2) { + printStackTraceAndExit("Incorrect definition of hash value for " + profileID); + } + + String digestAlgo = hashInfo[0].trim(); + String expectedHash = hashInfo[1].trim(); + try { + MessageDigest md = MessageDigest.getInstance(digestAlgo); + byte[] allInfoArray = allInfo.stream() + .sorted() + .collect(Collectors.joining("\n")) + .getBytes(StandardCharsets.UTF_8); + byte[] resultHashArray = md.digest(allInfoArray); + StringBuilder hexString = new StringBuilder(); + for (byte hashByte : resultHashArray) { + hexString.append(String.format("%02x", hashByte & 0xff)); + } + String resultHashHex = hexString.toString(); + if (debug != null) { + debug.println("\tCalculated hash for '" + profileID + "': " + resultHashHex); + debug.println("\tExpected hash for '" + profileID + "': " + expectedHash); + } + if (!resultHashHex.equalsIgnoreCase(expectedHash)) { + printStackTraceAndExit("Hex produced from profile is not the same is a " + + "base profile, so a hash value is mandatory."); + } + } catch (NoSuchAlgorithmException nsae) { + if (debug != null) { + debug.println("The hash algorithm specified for '" + + profileID + "' is not available."); + } + printStackTraceAndExit(nsae); } - // Provider without package names. - return providerName; } } @@ -1111,82 +1562,137 @@ private void listAvailableProfiles() { } } - /** - * List the RestrictedSecurity profile currently used. - */ - private void listUsedProfile() { - System.out.println(); - System.out.println("Utilized Restricted Security Profile Info:"); - System.out.println("=========================================="); - System.out.println("The Restricted Security profile used is: " + profileID); - System.out.println(); - printProfile(profileID); - } - private void printProfile(String profileToPrint) { + Set propertyNames = securityProps.stringPropertyNames(); + List descKeys = new ArrayList<>(); + List providers = new ArrayList<>(); + List restrictions = new ArrayList<>(); + for (String propertyName : propertyNames) { + if (propertyName.startsWith(profileToPrint + ".desc.") || propertyName.startsWith(profileToPrint + ".fips.")) { + descKeys.add(propertyName + securityProps.getProperty(propertyName)); + } else if (propertyName.startsWith(profileToPrint + ".jce.provider.")) { + providers.add(propertyName + securityProps.getProperty(propertyName)); + } else if (propertyName.startsWith(profileToPrint)) { + restrictions.add(propertyName + securityProps.getProperty(propertyName)); + } + } + System.out.println(profileToPrint + " Profile Info:"); System.out.println("=========================================="); - printProperty(profileToPrint + ".desc.name: ", - securityProps.getProperty(profileToPrint + ".desc.name")); - printProperty(profileToPrint + ".desc.default: ", - securityProps.getProperty(profileToPrint + ".desc.default")); - printProperty(profileToPrint + ".desc.fips: ", - securityProps.getProperty(profileToPrint + ".desc.fips")); - printProperty(profileToPrint + ".fips.mode: ", - securityProps.getProperty(profileToPrint + ".fips.mode")); - printProperty(profileToPrint + ".desc.number: ", - parseProperty(securityProps.getProperty(profileToPrint + ".desc.number"))); - printProperty(profileToPrint + ".desc.policy: ", - parseProperty(securityProps.getProperty(profileToPrint + ".desc.policy"))); - printProperty(profileToPrint + ".desc.sunsetDate: ", - parseProperty(securityProps.getProperty(profileToPrint + ".desc.sunsetDate"))); + for (String descKey : descKeys) { + System.out.println(descKey); + } System.out.println(); // List providers. System.out.println(profileToPrint + " Profile Providers:"); System.out.println("==============================================="); - for (int pNum = 1;; ++pNum) { - String providerInfo = securityProps - .getProperty(profileToPrint + ".jce.provider." + pNum); - - if ((providerInfo == null) || providerInfo.trim().isEmpty()) { - break; - } - printProperty(profileToPrint + ".jce.provider." + pNum + ": ", providerInfo); + for (String provider : providers) { + System.out.println(provider); } System.out.println(); // List profile restrictions. System.out.println(profileToPrint + " Profile Restrictions:"); System.out.println("=================================================="); - printProperty(profileToPrint + ".tls.disabledNamedCurves: ", - parseProperty(securityProps.getProperty(profileToPrint + ".tls.disabledNamedCurves"))); - printProperty(profileToPrint + ".tls.disabledAlgorithms: ", - parseProperty(securityProps.getProperty(profileToPrint + ".tls.disabledAlgorithms"))); - printProperty(profileToPrint + ".tls.ephemeralDHKeySize: ", - parseProperty(securityProps.getProperty(profileToPrint + ".tls.ephemeralDHKeySize"))); - printProperty(profileToPrint + ".tls.legacyAlgorithms: ", - parseProperty(securityProps.getProperty(profileToPrint + ".tls.legacyAlgorithms"))); - printProperty(profileToPrint + ".jce.certpath.disabledAlgorithms: ", - parseProperty(securityProps.getProperty(profileToPrint + ".jce.certpath.disabledAlgorithms"))); - printProperty(profileToPrint + ".jce.legacyAlgorithms: ", - parseProperty(securityProps.getProperty(profileToPrint + ".jce.legacyAlgorithms"))); + for (String restriction : restrictions) { + System.out.println(restriction); + } System.out.println(); + } - printProperty(profileToPrint + ".keystore.type: ", - parseProperty(securityProps.getProperty(profileToPrint + ".keystore.type"))); - printProperty(profileToPrint + ".javax.net.ssl.keyStore: ", - parseProperty(securityProps.getProperty(profileToPrint + ".javax.net.ssl.keyStore"))); - printProperty(profileToPrint + ".securerandom.provider: ", - parseProperty(securityProps.getProperty(profileToPrint + ".securerandom.provider"))); - printProperty(profileToPrint + ".securerandom.algorithm: ", - parseProperty(securityProps.getProperty(profileToPrint + ".securerandom.algorithm"))); - System.out.println(); + /** + * Only set a property if the value is not null. + * + * @param property the property to be set + * @param propertyKey the property key in the java.security file + * @return whether the property was set + */ + private boolean setProperty(String property, String propertyKey, List allInfo) { + if (debug != null) { + debug.println("Setting property: " + property); + } + String value = securityProps.getProperty(propertyKey); + value = parseProperty(value); + String newValue = null; + if (value != null) { + // Add to info to create hash. + allInfo.add(propertyKey + "=" + value); + + // Check if property overrides, adds to or removes from previous value. + String existingValue = profileProperties.get(property); + if (value.startsWith("+")) { + if (!isPropertyAppendable(property)) { + printStackTraceAndExit("Property '" + property + "' is not appendable."); + } else { + // Append additional values to property. + value = value.substring(1).trim(); + + // Take existing value of property into account, if applicable. + if (existingValue == null) { + printStackTraceAndExit("Property '" + property + "' does not exist in parent profile. Cannot append."); + } else if (existingValue.isBlank()) { + newValue = value; + } else { + newValue = (value.isBlank()) ? existingValue : existingValue + ", " + value; + } + } + } else if (value.startsWith("-")) { + if (!isPropertyAppendable(property)) { + printStackTraceAndExit("Property '" + property + "' is not appendable."); + } else { + // Remove values from property. + value = value.substring(1).trim(); + if (!value.isBlank()) { + List existingValues = Stream.of(existingValue.split(",")) + .map(v -> v.trim()) + .collect(Collectors.toList()); + String[] valuesToRemove = value.split(","); + for (String valueToRemove : valuesToRemove) { + if (!existingValues.remove(valueToRemove.trim())) { + printStackTraceAndExit("Value '" + valueToRemove + "' is not in existing values."); + } + } + newValue = String.join(",", existingValues); + } else { + // Nothing to do. Use existing value of property into account, if available. + if (existingValue == null) { + printStackTraceAndExit("Property '" + property + "' does not exist in parent profile. Cannot remove."); + } else if (existingValue.isBlank()) { + newValue = value; + } else { + newValue = existingValue; + } + } + } + } else { + newValue = value; + } + profileProperties.put(property, newValue); + return true; + } + if (debug != null) { + debug.println("Nothing to set. Value of property " + property + " is null."); + } + + return false; } - private void printProperty(String name, String value) { - String valueToPrint = (value.isEmpty()) ? "NOT AVAILABLE" : value; - System.out.println(name + valueToPrint); + private String getProperty(String property) { + return profileProperties.get(property); + } + + private static boolean isPropertyAppendable(String property) { + switch (property) { + case "jdkCertpathDisabledAlgorithms": + case "jdkSecurityLegacyAlgorithms": + case "jdkTlsDisabledAlgorithms": + case "jdkTlsDisabledNamedCurves": + case "jdkTlsLegacyAlgorithms": + return true; + default: + return false; + } } /** @@ -1203,64 +1709,76 @@ private static String parseProperty(String string) { return string; } - /** - * Check if the brackets are balanced. - * - * @param string input string for checking - * @return true if the brackets are balanced - */ - private static boolean areBracketsBalanced(String string) { - Deque deque = new LinkedList<>(); + private static void checkProviderFormat(String providerInfo, boolean update) { + final String nameRE = "[A-Za-z0-9.-]+"; + final String fileRE = "[A-Za-z0-9./\\\\${}]+"; + Pattern p = Pattern.compile( + "^(" + nameRE + ")" // provider name + + "\\s*" + + "(" + fileRE + ")?" // configuration file [optional] + + "\\s*" + + "(\\[" // constraints [optional] + + "\\s*" + + "([+-])?" // action [optional] + + "[A-Za-z0-9{}.=*|:,/_\\s-]+" // constraint definition + + "\\])?" + + "\\s*" + + "$"); + Matcher m = p.matcher(providerInfo); + if (m.find()) { + String providerName = m.group(1); + if (providerName.indexOf('.') <= 0) { + printStackTraceAndExit("Provider must be specified using" + + " the fully-qualified class name: " + providerName); + } - for (char ch : string.toCharArray()) { - switch (ch) { - case '{': - deque.addFirst('}'); - break; - case '[': - deque.addFirst(']'); - break; - case '(': - deque.addFirst(')'); - break; - case '}': - case ']': - case ')': - if (deque.isEmpty() || (deque.removeFirst().charValue() != ch)) { - return false; - } - break; - default: - break; + String action = m.group(4); + if (!update && !isNullOrBlank(action)) { + printStackTraceAndExit("You cannot add or remove to provider " + + m.group(1) + ". This is the base profile."); } + } else { + printStackTraceAndExit("Provider format is incorrect: " + providerInfo); } - return deque.isEmpty(); } + } - /** - * Check if the input string is asterisk (*). - * - * @param string input string for checking - * @return true if the input string is asterisk - */ - private static boolean isAsterisk(String string) { - return "*".equals(string); + /** + * A class representing the constraints of a provider. + */ + private static final class Constraint { + final String type; + final String algorithm; + final String attributes; + + Constraint(String type, String algorithm, String attributes) { + super(); + this.type = type; + this.algorithm = algorithm; + this.attributes = attributes; } - /** - * A class representing the constraints of a provider. - */ - private static final class Constraint { - final String type; - final String algorithm; - final String attributes; + @Override + public String toString() { + return "{" + type + ", " + algorithm + ", " + attributes + "}"; + } - Constraint(String type, String algorithm, String attributes) { - super(); - this.type = type; - this.algorithm = algorithm; - this.attributes = attributes; + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Constraint other) { + return Objects.equals(type, other.type) + && Objects.equals(algorithm, other.algorithm) + && Objects.equals(attributes, other.attributes); } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(type, algorithm, attributes); } } } diff --git a/src/java.base/share/classes/sun/security/jca/Providers.java b/src/java.base/share/classes/sun/security/jca/Providers.java index a6e94fdf502..a94d491c008 100644 --- a/src/java.base/share/classes/sun/security/jca/Providers.java +++ b/src/java.base/share/classes/sun/security/jca/Providers.java @@ -23,9 +23,17 @@ * questions. */ +/* + * =========================================================================== + * (c) Copyright IBM Corp. 2024, 2024 All Rights Reserved + * =========================================================================== + */ + package sun.security.jca; import java.security.Provider; + +import openj9.internal.security.RestrictedSecurity; import sun.security.x509.AlgorithmId; /** @@ -53,6 +61,7 @@ public class Providers { // triggers a getInstance() call (although that should not happen) providerList = ProviderList.EMPTY; providerList = ProviderList.fromSecurityProperties(); + RestrictedSecurity.checkHashValues(); } private Providers() { diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 1cd5fd8152b..74eb86d881b 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -157,6 +157,7 @@ RestrictedSecurity.NSS.140-2.securerandom.algorithm = PKCS11 RestrictedSecurity.OpenJCEPlusFIPS.FIPS140-3.desc.name = OpenJCEPlusFIPS Cryptographic Module FIPS 140-3 RestrictedSecurity.OpenJCEPlusFIPS.FIPS140-3.desc.default = true RestrictedSecurity.OpenJCEPlusFIPS.FIPS140-3.desc.fips = true +RestrictedSecurity.OpenJCEPlusFIPS.FIPS140-3.desc.hash = SHA256:f5b04d07f6fd5d11374b23dd96110b231d0241096563ae55cf3ea6fb1788d97c RestrictedSecurity.OpenJCEPlusFIPS.FIPS140-3.desc.number = Certificate #XXX RestrictedSecurity.OpenJCEPlusFIPS.FIPS140-3.desc.policy = https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/ RestrictedSecurity.OpenJCEPlusFIPS.FIPS140-3.desc.sunsetDate = 2026-09-21