/*
 * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.util;

import sun.security.ssl.SSLScope;
import sun.security.validator.Validator;

import java.lang.ref.SoftReference;
import java.security.AlgorithmParameters;
import java.security.CryptoPrimitive;
import java.security.Key;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.interfaces.ECKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Algorithm constraints for disabled algorithms property
 *
 * See the "jdk.certpath.disabledAlgorithms" specification in java.security
 * for the syntax of the disabled algorithm string.
 */
public class DisabledAlgorithmConstraints extends AbstractAlgorithmConstraints {
    private static final Debug debug = Debug.getInstance("certpath");

    // Disabled algorithm security property for certificate path
    public static final String PROPERTY_CERTPATH_DISABLED_ALGS =
            "jdk.certpath.disabledAlgorithms";

    // Legacy algorithm security property for certificate path and jar
    public static final String PROPERTY_SECURITY_LEGACY_ALGS =
            "jdk.security.legacyAlgorithms";

    // Disabled algorithm security property for TLS
    public static final String PROPERTY_TLS_DISABLED_ALGS =
            "jdk.tls.disabledAlgorithms";

    // Disabled algorithm security property for jar
    public static final String PROPERTY_JAR_DISABLED_ALGS =
            "jdk.jar.disabledAlgorithms";

    // Property for disabled EC named curves
    private static final String PROPERTY_DISABLED_EC_CURVES =
            "jdk.disabled.namedCurves";

    private static final Pattern INCLUDE_PATTERN = Pattern.compile("include " +
            PROPERTY_DISABLED_EC_CURVES, Pattern.CASE_INSENSITIVE);

    private static class CertPathHolder {
        static final DisabledAlgorithmConstraints CONSTRAINTS =
            new DisabledAlgorithmConstraints(PROPERTY_CERTPATH_DISABLED_ALGS);
    }

    private static class JarHolder {
        static final DisabledAlgorithmConstraints CONSTRAINTS =
            new DisabledAlgorithmConstraints(PROPERTY_JAR_DISABLED_ALGS);
    }

    private final Set<String> disabledAlgorithms;
    private final List<Pattern> disabledPatterns;
    private final Constraints algorithmConstraints;
    private volatile SoftReference<Map<String, Boolean>> cacheRef =
            new SoftReference<>(null);

    public static DisabledAlgorithmConstraints certPathConstraints() {
        return CertPathHolder.CONSTRAINTS;
    }

    public static DisabledAlgorithmConstraints jarConstraints() {
        return JarHolder.CONSTRAINTS;
    }

    /**
     * Initialize algorithm constraints with the specified security property.
     *
     * @param propertyName the security property name that define the disabled
     *        algorithm constraints
     */
    public DisabledAlgorithmConstraints(String propertyName) {
        this(propertyName, new AlgorithmDecomposer());
    }

    /**
     * Initialize algorithm constraints with the specified security property
     * for a specific usage type.
     *
     * @param propertyName the security property name that define the disabled
     *        algorithm constraints
     * @param decomposer an alternate AlgorithmDecomposer.
     */
    public DisabledAlgorithmConstraints(String propertyName,
            AlgorithmDecomposer decomposer) {
        super(decomposer);
        disabledAlgorithms = getAlgorithms(propertyName);

        // Support patterns only for jdk.tls.disabledAlgorithms
        if (PROPERTY_TLS_DISABLED_ALGS.equals(propertyName)) {
            disabledPatterns = getDisabledPatterns();
        } else {
            disabledPatterns = null;
        }

        // Check for alias
        for (String s : disabledAlgorithms) {
            Matcher matcher = INCLUDE_PATTERN.matcher(s);
            if (matcher.matches()) {
                disabledAlgorithms.remove(matcher.group());
                disabledAlgorithms.addAll(
                        getAlgorithms(PROPERTY_DISABLED_EC_CURVES));
                break;
            }
        }
        algorithmConstraints = new Constraints(propertyName, disabledAlgorithms);
    }

    /*
     * This only checks if the algorithm has been completely disabled.  If
     * there are keysize or other limit, this method allow the algorithm.
     */
    @Override
    public final boolean permits(Set<CryptoPrimitive> primitives,
            String algorithm, AlgorithmParameters parameters) {
        if (primitives == null || primitives.isEmpty()) {
            throw new IllegalArgumentException("The primitives cannot be null" +
                    " or empty.");
        }
        if (algorithm == null || algorithm.isEmpty()) {
            throw new IllegalArgumentException("No algorithm name specified");
        }

        if (!cachedCheckAlgorithm(algorithm)) {
            return false;
        }

        if (parameters != null) {
            return algorithmConstraints.permits(algorithm, parameters);
        }

        return true;
    }

    // Checks if algorithm is disabled for the given TLS scopes.
    public boolean permits(String algorithm, Set<SSLScope> scopes) {
        List<Constraint> list = algorithmConstraints.getConstraints(algorithm);
        return list == null || list.stream().allMatch(c -> c.permits(scopes));
    }

    /*
     * Checks if the key algorithm has been disabled or constraints have been
     * placed on the key.
     */
    @Override
    public final boolean permits(Set<CryptoPrimitive> primitives, Key key) {
        return checkConstraints(primitives, "", key, null);
    }

    /*
     * Checks if the key algorithm has been disabled or if constraints have
     * been placed on the key.
     */
    @Override
    public final boolean permits(Set<CryptoPrimitive> primitives,
            String algorithm, Key key, AlgorithmParameters parameters) {

        if (algorithm == null || algorithm.isEmpty()) {
            throw new IllegalArgumentException("No algorithm name specified");
        }

        return checkConstraints(primitives, algorithm, key, parameters);
    }

    public final void permits(String algorithm, AlgorithmParameters ap,
            ConstraintsParameters cp, boolean checkKey)
            throws CertPathValidatorException {
        permits(algorithm, cp, checkKey);
        if (ap != null) {
            permits(ap, cp);
        }
    }

    public void permits(AlgorithmParameters ap, ConstraintsParameters cp)
        throws CertPathValidatorException {

        switch (ap.getAlgorithm().toUpperCase(Locale.ENGLISH)) {
            case "RSASSA-PSS":
                permitsPSSParams(ap, cp);
                break;
            default:
                // unknown algorithm, just ignore
        }
    }

    private void permitsPSSParams(AlgorithmParameters ap,
        ConstraintsParameters cp) throws CertPathValidatorException {

        try {
            PSSParameterSpec pssParams =
                ap.getParameterSpec(PSSParameterSpec.class);
            String digestAlg = pssParams.getDigestAlgorithm();
            permits(digestAlg, cp, false);
            AlgorithmParameterSpec mgfParams = pssParams.getMGFParameters();
            if (mgfParams instanceof MGF1ParameterSpec) {
                String mgfDigestAlg =
                    ((MGF1ParameterSpec)mgfParams).getDigestAlgorithm();
                if (!mgfDigestAlg.equalsIgnoreCase(digestAlg)) {
                    permits(mgfDigestAlg, cp, false);
                }
            }
        } catch (InvalidParameterSpecException ipse) {
            // ignore
        }
    }

    public final void permits(String algorithm, ConstraintsParameters cp,
            boolean checkKey) throws CertPathValidatorException {
        if (checkKey) {
            // Check if named curves in the key are disabled.
            for (Key key : cp.getKeys()) {
                for (String curve : getNamedParametersFromKey(key)) {
                    if (!cachedCheckAlgorithm(curve)) {
                        throw new CertPathValidatorException(
                            "Algorithm constraints check failed on disabled " +
                                    "algorithm: " + curve,
                            null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
                    }
                }
            }
        }
        algorithmConstraints.permits(algorithm, cp, checkKey);
    }

    private static List<String> getNamedParametersFromKey(Key key) {
        return switch (key) {
            case ECKey ecKey -> {
                NamedCurve nc = CurveDB.lookup(ecKey.getParams());
                if (nc == null) {
                    yield List.of();
                }
                yield List.of(nc.getNameAndAliases());
            }
            default -> {
                String n = KeyUtil.getAlgorithm(key);
                if (n.equalsIgnoreCase(key.getAlgorithm())) {
                    yield List.of(n);
                }
                yield List.of(key.getAlgorithm(), n);
            }
        };
    }

    // Check algorithm constraints with key and algorithm
    private boolean checkConstraints(Set<CryptoPrimitive> primitives,
            String algorithm, Key key, AlgorithmParameters parameters) {

        if (primitives == null || primitives.isEmpty()) {
            throw new IllegalArgumentException("The primitives cannot be null" +
                    " or empty.");
        }

        if (key == null) {
            throw new IllegalArgumentException("The key cannot be null");
        }

        // check the signature algorithm with parameters
        if (algorithm != null && !algorithm.isEmpty()) {
            if (!permits(primitives, algorithm, parameters)) {
                return false;
            }
        }

        // check the key algorithm
        if (!permits(primitives, KeyUtil.getAlgorithm(key), null)) {
            return false;
        }

        // If this is an elliptic curve, check if it is disabled
        for (String curve : getNamedParametersFromKey(key)) {
            if (!permits(primitives, curve, null)) {
                return false;
            }
        }

        // check the key constraints
        return algorithmConstraints.permits(key);
    }


    /**
     * Key and Certificate Constraints
     *
     * When passing a Key to permit(), the boolean return values follow the
     * same as the interface class AlgorithmConstraints.permit().  This is to
     * maintain compatibility:
     * 'true' means the operation is allowed.
     * 'false' means it failed the constraints and is disallowed.
     *
     * When passing ConstraintsParameters through permit(), an exception
     * will be thrown on a failure to better identify why the operation was
     * disallowed.
     */
    private static class Constraints {
        private final Map<String, List<Constraint>> constraintsMap = new HashMap<>();

        private static class Holder {
            private static final Pattern DENY_AFTER_PATTERN = Pattern.compile(
                    "denyAfter\\s+(\\d{4})-(\\d{2})-(\\d{2})");
        }

        public Constraints(String propertyName, Set<String> constraintSet) {
            for (String constraintEntry : constraintSet) {
                if (constraintEntry == null || constraintEntry.isEmpty()) {
                    continue;
                }

                constraintEntry = constraintEntry.trim();
                if (debug != null) {
                    debug.println("Constraints: " + constraintEntry);
                }

                // Check if constraint is a complete disabling of an
                // algorithm or has conditions.
                int space = constraintEntry.indexOf(' ');
                String algorithm = AlgorithmDecomposer.decomposeDigestName(
                        space > 0 ? constraintEntry.substring(0, space) :
                                constraintEntry);
                List<Constraint> constraintList =
                        constraintsMap.getOrDefault(
                                algorithm.toUpperCase(Locale.ENGLISH),
                                new ArrayList<>(1));

                // Consider the impact of algorithm aliases.
                for (String alias : AlgorithmDecomposer.getAliases(algorithm)) {
                    constraintsMap.putIfAbsent(
                            alias.toUpperCase(Locale.ENGLISH), constraintList);
                }

                // If there is no whitespace, it is an algorithm name; however,
                // if there is a whitespace, could be a multi-word EC curve too.
                if (space <= 0 || CurveDB.lookup(constraintEntry) != null) {
                    constraintList.add(new DisabledConstraint(algorithm));
                    continue;
                }

                String policy = constraintEntry.substring(space + 1);

                // Convert constraint conditions into Constraint classes
                Constraint c, lastConstraint = null;
                // Allow only one jdkCA entry per constraint entry
                boolean jdkCALimit = false;
                // Allow only one denyAfter entry per constraint entry
                boolean denyAfterLimit = false;

                for (String entry : policy.split("&")) {
                    entry = entry.trim();

                    Matcher matcher;
                    if (entry.startsWith("keySize")) {
                        if (debug != null) {
                            debug.println("Constraints set to keySize: " +
                                    entry);
                        }
                        StringTokenizer tokens = new StringTokenizer(entry);
                        if (!"keySize".equals(tokens.nextToken())) {
                            throw new IllegalArgumentException("Error in " +
                                    "security property. Constraint unknown: " +
                                    entry);
                        }
                        c = new KeySizeConstraint(algorithm,
                                KeySizeConstraint.Operator.of(tokens.nextToken()),
                                Integer.parseInt(tokens.nextToken()));

                    } else if (entry.equalsIgnoreCase("jdkCA")) {
                        if (debug != null) {
                            debug.println("Constraints set to jdkCA.");
                        }
                        if (jdkCALimit) {
                            throw new IllegalArgumentException("Only one " +
                                    "jdkCA entry allowed in property. " +
                                    "Constraint: " + constraintEntry);
                        }
                        c = new jdkCAConstraint(algorithm);
                        jdkCALimit = true;

                    } else if (entry.startsWith("denyAfter") &&
                            (matcher = Holder.DENY_AFTER_PATTERN.matcher(entry))
                                    .matches()) {
                        if (debug != null) {
                            debug.println("Constraints set to denyAfter");
                        }
                        if (denyAfterLimit) {
                            throw new IllegalArgumentException("Only one " +
                                    "denyAfter entry allowed in property. " +
                                    "Constraint: " + constraintEntry);
                        }
                        int year = Integer.parseInt(matcher.group(1));
                        int month = Integer.parseInt(matcher.group(2));
                        int day = Integer.parseInt(matcher.group(3));
                        c = new DenyAfterConstraint(algorithm, year, month,
                                day);
                        denyAfterLimit = true;
                    } else if (entry.startsWith("usage")) {
                        String[] s = (entry.substring(5)).trim().split(" ");
                        c = new UsageConstraint(algorithm, s, propertyName);
                        if (debug != null) {
                            debug.println("Constraints usage length is " + s.length);
                        }
                    } else {
                        throw new IllegalArgumentException("Error in security" +
                                " property. Constraint unknown: " + entry);
                    }

                    // Link multiple conditions for a single constraint
                    // into a linked list.
                    if (lastConstraint == null) {
                        constraintList.add(c);
                    } else {
                        lastConstraint.nextConstraint = c;
                    }
                    lastConstraint = c;
                }
            }
        }

        // Get applicable constraints based off the algorithm
        private List<Constraint> getConstraints(String algorithm) {
            return constraintsMap.get(algorithm.toUpperCase(Locale.ENGLISH));
        }

        // Check if KeySizeConstraints permit the specified key
        public boolean permits(Key key) {
            List<Constraint> list = getConstraints(KeyUtil.getAlgorithm(key));
            if (list == null) {
                return true;
            }
            for (Constraint constraint : list) {
                if (!constraint.permits(key)) {
                    if (debug != null) {
                        debug.println("Constraints: failed key size " +
                                "constraint check " + KeyUtil.getKeySize(key));
                    }
                    return false;
                }
            }
            return true;
        }

        // Check if constraints permit this AlgorithmParameters.
        public boolean permits(String algorithm, AlgorithmParameters aps) {
            List<Constraint> list = getConstraints(algorithm);
            if (list == null) {
                return true;
            }

            for (Constraint constraint : list) {
                if (!constraint.permits(aps)) {
                    if (debug != null) {
                        debug.println("Constraints: failed algorithm " +
                                "parameters constraint check " + aps);
                    }

                    return false;
                }
            }

            return true;
        }

        public void permits(String algorithm, ConstraintsParameters cp,
                boolean checkKey) throws CertPathValidatorException {

            if (debug != null) {
                debug.println("Constraints.permits(): " + algorithm + ", "
                              + cp.toString());
            }

            // Get all signature algorithms to check for constraints
            Set<String> algorithms = new HashSet<>();
            if (algorithm != null) {
                algorithms.addAll(AlgorithmDecomposer.decomposeName(algorithm));
                algorithms.add(algorithm);
            }

            if (checkKey) {
                for (Key key : cp.getKeys()) {
                    algorithms.add(KeyUtil.getAlgorithm(key));
                }
            }

            // Check all applicable constraints
            for (String alg : algorithms) {
                List<Constraint> list = getConstraints(alg);
                if (list == null) {
                    continue;
                }
                for (Constraint constraint : list) {
                    if (!checkKey && constraint instanceof KeySizeConstraint) {
                        continue;
                    }
                    constraint.permits(cp);
                }
            }
        }
    }

    /**
     * This abstract Constraint class for algorithm-based checking
     * may contain one or more constraints.  If the '&' on the {@Security}
     * property is used, multiple constraints have been grouped together
     * requiring all the constraints to fail for the check to be disallowed.
     *
     * If the class contains multiple constraints, the next constraint
     * is stored in {@code nextConstraint} in linked-list fashion.
     */
    private abstract static class Constraint {
        String algorithm;
        Constraint nextConstraint = null;

        // operator
        enum Operator {
            EQ,         // "=="
            NE,         // "!="
            LT,         // "<"
            LE,         // "<="
            GT,         // ">"
            GE;         // ">="

            static Operator of(String s) {
                switch (s) {
                    case "==":
                        return EQ;
                    case "!=":
                        return NE;
                    case "<":
                        return LT;
                    case "<=":
                        return LE;
                    case ">":
                        return GT;
                    case ">=":
                        return GE;
                }

                throw new IllegalArgumentException("Error in security " +
                        "property. " + s + " is not a legal Operator");
            }
        }

        /**
         * Check if an algorithm constraint is permitted with a given key.
         *
         * If the check inside of {@code permit()} fails, it must call
         * {@code next()} with the same {@code Key} parameter passed if
         * multiple constraints need to be checked.
         *
         * @param key Public key
         * @return 'true' if constraint is allowed, 'false' if disallowed.
         */
        public boolean permits(Key key) {
            return true;
        }

        /**
         * Check if the algorithm constraint permits a given cryptographic
         * parameters.
         *
         * @param parameters the cryptographic parameters
         * @return 'true' if the cryptographic parameters is allowed,
         *         'false' otherwise.
         */
        public boolean permits(AlgorithmParameters parameters) {
            return true;
        }

        /**
         * Check if the algorithm constraint permits the given TLS scopes.
         *
         * @param scopes TLS scopes
         * @return 'true' if TLS scopes are allowed,
         *         'false' otherwise.
         */
        public boolean permits(Set<SSLScope> scopes) {
            return true;
        }

        /**
         * Check if an algorithm constraint is permitted with a given
         * ConstraintsParameters.
         *
         * If the check inside of {@code permits()} fails, it must call
         * {@code next()} with the same {@code ConstraintsParameters}
         * parameter passed if multiple constraints need to be checked.
         *
         * @param cp ConstraintsParameter containing certificate info
         * @throws CertPathValidatorException if constraint disallows.
         *
         */
        public abstract void permits(ConstraintsParameters cp)
                throws CertPathValidatorException;

        /**
         * Recursively check if the constraints are allowed.
         *
         * If {@code nextConstraint} is non-null, this method will
         * call {@code nextConstraint}'s {@code permits()} to check if the
         * constraint is allowed or denied.  If the constraint's
         * {@code permits()} is allowed, this method will exit this and any
         * recursive next() calls, returning 'true'.  If the constraints called
         * were disallowed, the last constraint will throw
         * {@code CertPathValidatorException}.
         *
         * @param cp ConstraintsParameters
         * @return 'true' if constraint allows the operation, 'false' if
         * we are at the end of the constraint list or,
         * {@code nextConstraint} is null.
         */
        boolean next(ConstraintsParameters cp)
                throws CertPathValidatorException {
            if (nextConstraint != null) {
                nextConstraint.permits(cp);
                return true;
            }
            return false;
        }

        /**
         * Recursively check if this constraint is allowed,
         *
         * If {@code nextConstraint} is non-null, this method will
         * call {@code nextConstraint}'s {@code permit()} to check if the
         * constraint is allowed or denied.  If the constraint's
         * {@code permit()} is allowed, this method will exit this and any
         * recursive next() calls, returning 'true'.  If the constraints
         * called were disallowed the check will exit with 'false'.
         *
         * @param key Public key
         * @return 'true' if constraint allows the operation, 'false' if
         * the constraint denies the operation.
         */
        boolean next(Key key) {
            return nextConstraint != null && nextConstraint.permits(key);
        }
    }

    /*
     * This class contains constraints dealing with the certificate chain
     * of the certificate.
     */
    private static class jdkCAConstraint extends Constraint {
        jdkCAConstraint(String algo) {
            algorithm = algo;
        }

        /*
         * Check if ConstraintsParameters has a trusted match, if it does
         * call next() for any following constraints. If it does not, exit
         * as this constraint(s) does not restrict the operation.
         */
        @Override
        public void permits(ConstraintsParameters cp)
                throws CertPathValidatorException {
            if (debug != null) {
                debug.println("jdkCAConstraints.permits(): " + algorithm);
            }

            // Check if any certs chain back to at least one trust anchor in
            // cacerts
            if (cp.anchorIsJdkCA()) {
                if (next(cp)) {
                    return;
                }
                throw new CertPathValidatorException(
                        "Algorithm constraints check failed on certificate " +
                        "anchor limits. " + algorithm + cp.extendedExceptionMsg(),
                        null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
            }
        }
    }

    /*
     * This class handles the denyAfter constraint.  The date is in the UTC/GMT
     * timezone.
     */
    private static class DenyAfterConstraint extends Constraint {
        private final ZonedDateTime zdt;
        private final Instant denyAfterDate;

        DenyAfterConstraint(String algo, int year, int month, int day) {

            algorithm = algo;

            if (debug != null) {
                debug.println("DenyAfterConstraint read in as: year " +
                        year + ", month = " + month + ", day = " + day);
            }

            try {
                zdt = ZonedDateTime
                    .of(year, month, day, 0, 0, 0, 0, ZoneId.of("GMT"));
                denyAfterDate = zdt.toInstant();
            } catch (DateTimeException dte) {
                throw new IllegalArgumentException(
                    "Invalid denyAfter date", dte);
            }

            if (debug != null) {
                debug.println("DenyAfterConstraint date set to: " +
                        zdt.toLocalDate());
            }
        }

        /*
         * Checking that the provided date is not beyond the constraint date.
         * The provided date can be the PKIXParameter date if given,
         * otherwise it is the current date.
         *
         * If the constraint disallows, call next() for any following
         * constraints. Throw an exception if this is the last constraint.
         */
        @Override
        public void permits(ConstraintsParameters cp)
                throws CertPathValidatorException {
            Instant currentDate;

            if (cp.getDate() != null) {
                currentDate = cp.getDate().toInstant();
            } else {
                currentDate = Instant.now();
            }

            if (!denyAfterDate.isAfter(currentDate)) {
                if (next(cp)) {
                    return;
                }
                throw new CertPathValidatorException(
                        "denyAfter constraint check failed: " + algorithm +
                        " used with Constraint date: " +
                        zdt.toLocalDate() + "; params date: " +
                        currentDate + cp.extendedExceptionMsg(),
                        null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
            }
        }

        /*
         * Return result if the constraint's date is beyond the current date
         * in UTC timezone.
         */
        @Override
        public boolean permits(Key key) {
            if (next(key)) {
                return true;
            }
            if (debug != null) {
                debug.println("DenyAfterConstraints.permits(): " + algorithm);
            }

            return denyAfterDate.isAfter(Instant.now());
        }
    }

    /*
     * The usage constraint is for the "usage" keyword.  It checks against the
     * variant value in ConstraintsParameters and against TLS scopes.
     */
    private static class UsageConstraint extends Constraint {
        String[] usages;
        Set<SSLScope> scopes;

        UsageConstraint(
                String algorithm, String[] usages, String propertyName) {
            this.algorithm = algorithm;

            // Support TLS scopes only for jdk.tls.disabledAlgorithms property.
            if (PROPERTY_TLS_DISABLED_ALGS.equals(propertyName)) {
                for (String usage : usages) {
                    SSLScope scope = SSLScope.nameOf(usage);

                    if (scope != null) {
                        if (this.scopes == null) {
                            this.scopes = new HashSet<>(usages.length);
                        }
                        this.scopes.add(scope);
                    } else {
                        this.usages = usages;
                    }
                }

                if (this.scopes != null && this.usages != null) {
                    throw new IllegalArgumentException(
                            "Can't mix TLS protocol specific constraints"
                            + " with other usage constraints");
                }

            } else {
                this.usages = usages;
            }
        }

        @Override
        public boolean permits(Set<SSLScope> scopes) {
            if (this.scopes == null || scopes == null) {
                return true;
            }

            return Collections.disjoint(this.scopes, scopes);
        }

        @Override
        public void permits(ConstraintsParameters cp)
                throws CertPathValidatorException {
            String variant = cp.getVariant();
            for (String usage : usages) {

                boolean match = false;
                switch (usage.toLowerCase(Locale.ENGLISH)) {
                    case "tlsserver":
                        match = variant.equals(Validator.VAR_TLS_SERVER);
                        break;
                    case "tlsclient":
                        match = variant.equals(Validator.VAR_TLS_CLIENT);
                        break;
                    case "signedjar":
                        match =
                            variant.equals(Validator.VAR_CODE_SIGNING) ||
                            variant.equals(Validator.VAR_TSA_SERVER);
                        break;
                }

                if (debug != null) {
                    debug.println("Checking if usage constraint \"" + usage +
                            "\" matches \"" + cp.getVariant() + "\"");
                    if (Debug.isVerbose()) {
                        // Because usage checking can come from many places
                        // a stack trace is very helpful.
                        (new Exception()).printStackTrace(debug.getPrintStream());
                    }
                }
                if (match) {
                    if (next(cp)) {
                        return;
                    }
                    throw new CertPathValidatorException("Usage constraint " +
                            usage + " check failed: " + algorithm +
                            cp.extendedExceptionMsg(),
                            null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
                }
            }
        }
    }

    /*
     * This class contains constraints dealing with the key size
     * support limits per algorithm.   e.g.  "keySize <= 1024"
     */
    private static class KeySizeConstraint extends Constraint {

        private final int minSize;          // the minimal available key size
        private final int maxSize;          // the maximal available key size
        private int prohibitedSize = -1;    // unavailable key sizes

        public KeySizeConstraint(String algo, Operator operator, int length) {
            algorithm = algo;
            switch (operator) {
                case EQ:      // an unavailable key size
                    this.minSize = 0;
                    this.maxSize = Integer.MAX_VALUE;
                    prohibitedSize = length;
                    break;
                case NE:
                    this.minSize = length;
                    this.maxSize = length;
                    break;
                case LT:
                    this.minSize = length;
                    this.maxSize = Integer.MAX_VALUE;
                    break;
                case LE:
                    this.minSize = length + 1;
                    this.maxSize = Integer.MAX_VALUE;
                    break;
                case GT:
                    this.minSize = 0;
                    this.maxSize = length;
                    break;
                case GE:
                    this.minSize = 0;
                    this.maxSize = length > 1 ? (length - 1) : 0;
                    break;
                default:
                    // unlikely to happen
                    this.minSize = Integer.MAX_VALUE;
                    this.maxSize = -1;
            }
        }

        /*
         * For each key, check if each constraint fails and check if there is
         * a linked constraint. Any permitted constraint will exit the linked
         * list to allow the operation.
         */
        @Override
        public void permits(ConstraintsParameters cp)
                throws CertPathValidatorException {
            for (Key key : cp.getKeys()) {
                if (!permitsImpl(key)) {
                    if (nextConstraint != null) {
                        nextConstraint.permits(cp);
                        continue;
                    }
                    throw new CertPathValidatorException(
                        "Algorithm constraints check failed on keysize limits: " +
                        algorithm + " " + KeyUtil.getKeySize(key) + " bit key" +
                        cp.extendedExceptionMsg(),
                        null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
                }
            }
        }


        // Check if key constraint disable the specified key
        // Uses old style permit()
        @Override
        public boolean permits(Key key) {
            // If we recursively find a constraint that permits us to use
            // this key, return true and skip any other constraint checks.
            if (nextConstraint != null && nextConstraint.permits(key)) {
                return true;
            }
            if (debug != null) {
                debug.println("KeySizeConstraints.permits(): " + algorithm);
            }

            return permitsImpl(key);
        }

        @Override
        public boolean permits(AlgorithmParameters parameters) {
            String paramAlg = parameters.getAlgorithm();
            if (!algorithm.equalsIgnoreCase(parameters.getAlgorithm())) {
                // Consider the impact of the algorithm aliases.
                Collection<String> aliases =
                        AlgorithmDecomposer.getAliases(algorithm);
                if (!aliases.contains(paramAlg)) {
                    return true;
                }
            }

            int keySize = KeyUtil.getKeySize(parameters);
            if (keySize == 0) {
                return false;
            } else if (keySize > 0) {
                return !((keySize < minSize) || (keySize > maxSize) ||
                    (prohibitedSize == keySize));
            }   // Otherwise, the key size is not accessible or determined.
                // Conservatively, please don't disable such keys.

            return true;
        }

        private boolean permitsImpl(Key key) {
            // Verify this constraint is for this public key algorithm
            if (algorithm.compareToIgnoreCase(key.getAlgorithm()) != 0) {
                return true;
            }

            int size = KeyUtil.getKeySize(key);
            if (size == 0) {
                return false;    // we don't allow any key of size 0.
            } else if (size > 0) {
                return !((size < minSize) || (size > maxSize) ||
                    (prohibitedSize == size));
            }   // Otherwise, the key size is not accessible. Conservatively,
                // please don't disable such keys.

            return true;
        }
    }

    private boolean cachedCheckAlgorithm(String algorithm) {
        Map<String, Boolean> cache;
        if ((cache = cacheRef.get()) == null) {
            synchronized (this) {
                if ((cache = cacheRef.get()) == null) {
                    cache = new ConcurrentHashMap<>();
                    cacheRef = new SoftReference<>(cache);
                }
            }
        }
        Boolean result = cache.get(algorithm);
        if (result != null) {
            return result;
        }
        // We won't check patterns if algorithm check fails.
        result = checkAlgorithm(disabledAlgorithms, algorithm, decomposer)
                && checkDisabledPatterns(algorithm);
        cache.put(algorithm, result);
        return result;
    }

    private boolean checkDisabledPatterns(final String algorithm) {
        return disabledPatterns == null || disabledPatterns.stream().noneMatch(
                p -> p.matcher(algorithm).matches());
    }

    private List<Pattern> getDisabledPatterns() {
        List<Pattern> ret = null;
        List<String> patternStrings = new ArrayList<>(4);

        for (String p : disabledAlgorithms) {
            if (p.contains("*")) {
                if (!p.startsWith("TLS_")) {
                    throw new IllegalArgumentException(
                            "Wildcard pattern must start with \"TLS_\"");
                }
                patternStrings.add(p);
            }
        }

        if (!patternStrings.isEmpty()) {
            ret = new ArrayList<>(patternStrings.size());

            for (String p : patternStrings) {
                // Exclude patterns from algorithm code flow.
                disabledAlgorithms.remove(p);

                // Ignore all regex characters but asterisk.
                ret.add(Pattern.compile(
                        "^\\Q" + p.replace("*", "\\E.*\\Q") + "\\E$"));
            }
        }

        return ret;
    }

    /*
     * This constraint is used for the complete disabling of the algorithm.
     */
    private static class DisabledConstraint extends Constraint {
        DisabledConstraint(String algo) {
            algorithm = algo;
        }

        @Override
        public void permits(ConstraintsParameters cp)
                throws CertPathValidatorException {
            throw new CertPathValidatorException(
                    "Algorithm constraints check failed on disabled " +
                            "algorithm: " + algorithm + cp.extendedExceptionMsg(),
                    null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
        }

        @Override
        public boolean permits(Key key) {
            return false;
        }
    }
}
