/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2022 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.installer.postinstall.task;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.dmr.ModelNode;
import org.jboss.installer.auto.AutomaticInstallationParsingException;
import org.jboss.installer.core.FlatListPostInstallConfig;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.postinstall.CliPostInstallTask;
import org.jboss.installer.postinstall.TaskPrinter;
import org.jboss.installer.postinstall.server.EmbeddedServer;
import org.jboss.installer.postinstall.task.utils.CredentialStoreUtil;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;

import static org.jboss.as.controller.operations.common.Util.createEmptyOperation;
import static org.wildfly.security.ssl.CipherSuiteSelector.OPENSSL_DEFAULT_CIPHER_SUITE_NAMES;

public abstract class AbstractHttpsEnableTask implements CliPostInstallTask {

    public static final String DEFAULT_CIPHER_SUITES_TLS12 = "DEFAULT";
    public static final String DEFAULT_CIPHER_NAMES_TLS13 = OPENSSL_DEFAULT_CIPHER_SUITE_NAMES;
    public static final String DN_DEFAULT = "Unknown";
    public static final String JBOSS_SERVER_CONFIG_DIR = "jboss.server.config.dir";
    public static final String JBOSS_DOMAIN_CONFIG_DIR = "jboss.domain.config.dir";
    public static final String KEY_ALG = "RSA";
    public static final int KEY_SIZE = 2048;
    public static final String SERVER_KEY_ALIAS = "serverkey";
    public static final String CLIENT_KEY_ALIAS = "clientkey";
    public static final String PROTOCOL_TLSv12 = "TLSv1.2";
    public static final String PROTOCOL_TLSv13 = "TLSv1.3";

    protected List<Pair<ModelNode, String>> getCommonOperations(InstallationData data, EmbeddedServer server,
                                                                String sslContextName,
                                                                String credentialAliasKeyStorePassword,
                                                                String credentialAliasTrustStorePassword,
                                                                String defaultKeyStoreFileName, String keyStoreName,
                                                                String keyManagerName,
                                                                String defaultTrustStoreFileName, String trustStoreName,
                                                                String trustManagerName,
                                                                TaskPrinter printer) throws OperationFailedException {
        return getCommonOperations(data, server, sslContextName, credentialAliasKeyStorePassword, credentialAliasTrustStorePassword,
                defaultKeyStoreFileName, keyStoreName, keyManagerName, defaultTrustStoreFileName, trustStoreName,
                trustManagerName, true, printer);
    }

    protected List<Pair<ModelNode, String>> getCommonOperations(InstallationData data, EmbeddedServer server,
                                                                String sslContextName,
                                                                String credentialAliasKeyStorePassword,
                                                                String credentialAliasTrustStorePassword,
                                                                String defaultKeyStoreFileName, String keyStoreName,
                                                                String keyManagerName,
                                                                String defaultTrustStoreFileName, String trustStoreName,
                                                                String trustManagerName,
                                                                boolean supportsAdvancedModifiableKeyStoreOperations,
                                                                TaskPrinter printer) throws OperationFailedException {
        final Config config = data.getConfig(getConfigClass());
        final Optional<CredentialStoreInstallTask.Config> csConfig
                = Optional.ofNullable(data.getConfig(CredentialStoreInstallTask.Config.class));

        final String keyStorePath = config.isDoGenerateStore() ? defaultKeyStoreFileName : config.getKeyStorePath();
        final String relativeTo = config.isDoGenerateStore() ? server.isDomain() ? JBOSS_DOMAIN_CONFIG_DIR: JBOSS_SERVER_CONFIG_DIR : null;

        final List<Pair<ModelNode, String>> ops = new ArrayList<>();

        if (csConfig.isPresent() && !server.aliasExists(credentialAliasKeyStorePassword, csConfig.get().getStoreName())) {
            printer.print("tasks.application_ssl.store_password",csConfig.get().getStoreName(), credentialAliasKeyStorePassword);
            server.encryptPassword(credentialAliasKeyStorePassword, csConfig.get().getStoreName(), config.getKeyStorePassword());
        }

        final ModelNode addKeystoreOp = getAddKeystoreOp(keyStoreName, keyStorePath, relativeTo, csConfig, credentialAliasKeyStorePassword, config.getKeyStorePassword());
        ops.add(Pair.of(addKeystoreOp, "Add keystore"));

        final ModelNode addKeyManagerOp = getAddKeymanagerOp(keyManagerName, keyStoreName, csConfig, credentialAliasKeyStorePassword, config.getKeyStorePassword());
        ops.add(Pair.of(addKeyManagerOp, "Add keymanager"));

        if (config.isDoGenerateStore() && supportsAdvancedModifiableKeyStoreOperations) {
            // generate the keystore only once for standalone and once for domain
            final boolean wasKeystoreGenerated = (server.isDomain() && config.isKeystoreGeneratedForDomain())
                            || (!server.isDomain() && config.isKeystoreGeneratedForStandalone());
            if (!wasKeystoreGenerated) {
                printer.print("tasks.application_ssl.generate_keystore");
                // generate key pair
                ops.add(Pair.of(getGenerateKeyPairOp(config.getDn(), config.getValidity(), keyStoreName), "Generate new key pair"));
                // store keystore
                ops.add(Pair.of(getStoreKeystoreOp(keyStoreName), "Store keystore"));
                // export new certificate from the key store
                final String certName = keyStorePath + ".pem";
                final String csrName = keyStorePath + ".csr";
                ops.add(Pair.of(getExportCertificateOp(new File(certName), relativeTo, keyStoreName), "Export certificate"));
                // generate signing request from the key store
                ops.add(Pair.of(getSigningRequestOp(new File(csrName), relativeTo, keyStoreName), "Generate signing request"));
            }
            if (server.isDomain()) {
                config.setKeystoreGeneratedForDomain(true);
            } else {
                config.setKeystoreGeneratedForStandalone(true);
            }
        }

        String usedTrustManagerName = null;
        // mutual/two-way SSL
        if (config.getTrustStorePath() != null || config.getClientCertPath() != null) {
            final String trustStorePath = config.getTrustStorePath() == null ? defaultTrustStoreFileName : config.getTrustStorePath();

            if (csConfig.isPresent() && !server.aliasExists(credentialAliasTrustStorePassword, csConfig.get().getStoreName())) {
                printer.print("tasks.application_ssl.store_password",csConfig.get().getStoreName(), credentialAliasTrustStorePassword);
                server.encryptPassword(credentialAliasTrustStorePassword, csConfig.get().getStoreName(), config.getTrustStorePassword());
            }

            //  add trust-store
            final ModelNode addTrustStoreOp = getAddKeystoreOp(trustStoreName, trustStorePath, relativeTo, csConfig, credentialAliasTrustStorePassword, config.getTrustStorePassword());
            ops.add(Pair.of(addTrustStoreOp, "Add truststore"));

            // import the client certificate
            if (config.getClientCertPath() != null && supportsAdvancedModifiableKeyStoreOperations) {
                final boolean wasClientCertImported = (server.isDomain() && config.isClientCertImportedForDomain())
                        || (!server.isDomain() && config.isClientCertImportedForStandalone());
                if (! wasClientCertImported) {
                    final ModelNode importCertOp = getImportCertificateOp(config.getClientCertPath(), true, config.isValidateCertificate(), trustStoreName);
                    ops.add(Pair.of(importCertOp, "Import client certificate"));
                    // store the trust store
                    ops.add(Pair.of(getStoreKeystoreOp(trustStoreName), "Store truststore"));
                    // import client certificate only once
                    config.setClientCertImportedForStandalone(true);
                }
                if (server.isDomain()) {
                    config.setClientCertImportedForDomain(true);
                } else {
                    config.setClientCertImportedForStandalone(true);
                }
            }

            // trust-manager
            ops.add(Pair.of(getAddTrustManagerOp(trustManagerName, trustStoreName), "Add trust manager"));
            usedTrustManagerName = trustManagerName;
        }

        final ModelNode addSslContextOp = getAddSslContextOp(config.getProtocols(), config.getCipherSuites(), config.getTls13cipherNames(),
                usedTrustManagerName, sslContextName, keyManagerName);
        ops.add(Pair.of(addSslContextOp, "Add ssl context"));

        return ops;
    }

    private ModelNode getAddKeystoreOp(String keystoreName, String path, String relativeTo, Optional<CredentialStoreInstallTask.Config> storeConfig, String alias, String password) {
        // /subsystem=elytron/key-store=test:add(credential-reference={alias="", store=""}, path=, relative-to=)
        // /subsystem=elytron/key-store=test:add(credential-reference={clear-text=""}, path=, relative-to=)
        final ModelNode addKsOp = createEmptyOperation("add",
                PathAddress.pathAddress("subsystem", "elytron").append("key-store", keystoreName));
        addKsOp.get("path").set(path);
        if (relativeTo != null) {
            addKsOp.get("relative-to").set(relativeTo);
        }
        CredentialStoreUtil.addCredReference(addKsOp, storeConfig, alias, password, "credential-reference");
        addKsOp.get(ModelDescriptionConstants.OPERATION_HEADERS).get("allow-resource-service-restart").set(true);
        return addKsOp;
    }

    private ModelNode getAddTrustManagerOp(String trustManagerName, String keyStoreName) {
        // /subsystem=elytron/trust-manager=test:add(key-store=)
        final ModelNode addTMOp = createEmptyOperation("add",
                PathAddress.pathAddress("subsystem", "elytron").append("trust-manager", trustManagerName));
        addTMOp.get("key-store").set(keyStoreName);
        addTMOp.get(ModelDescriptionConstants.OPERATION_HEADERS).get("allow-resource-service-restart").set(true);
        return addTMOp;
    }

    private ModelNode getAddKeymanagerOp(String keyManagerName, String keyStoreName, Optional<CredentialStoreInstallTask.Config> storeConfig, String alias, String password) {
        // /subsystem=elytron/key-manager=test:add(credential-reference={alias="", store=""}, path=)
        // /subsystem=elytron/key-manager=test:add(credential-reference={clear-text="foobar"}, path=)
        final ModelNode addKmOp = createEmptyOperation("add",
                PathAddress.pathAddress("subsystem", "elytron").append("key-manager", keyManagerName));
        CredentialStoreUtil.addCredReference(addKmOp, storeConfig, alias, password, "credential-reference");
        addKmOp.get("key-store").set(keyStoreName);
        addKmOp.get(ModelDescriptionConstants.OPERATION_HEADERS).get("allow-resource-service-restart").set(true);
        return addKmOp;
    }

    private ModelNode getGenerateKeyPairOp(String dn, String validity, String keyStoreName) {
        // /subsystem=elytron/key-store=test:generate-key-pair(distinguished-name=, algorithm=, key-size=, alias=, validity=)
        final ModelNode generateKPOp = createEmptyOperation("generate-key-pair",
                PathAddress.pathAddress("subsystem", "elytron").append("key-store", keyStoreName));
        generateKPOp.get("distinguished-name").set(dn);
        // algorithm and key-size are hard-coded
        generateKPOp.get("algorithm").set(KEY_ALG);
        generateKPOp.get("key-size").set(KEY_SIZE);
        generateKPOp.get("alias").set(SERVER_KEY_ALIAS);
        if (validity != null && validity.length() > 0) {
            generateKPOp.get("validity").set(validity);
        }
        generateKPOp.get(ModelDescriptionConstants.OPERATION_HEADERS).get("allow-resource-service-restart").set(true);
        return generateKPOp;
    }

    private ModelNode getImportCertificateOp(String clientCertPath, boolean trust, boolean validateCertificate, String trustStoreName) {
        // subsystem=elytron/key-store=test:import-certificate(alias=, path=, trust-cacerts=, validate=)
        final ModelNode importCertOp = createEmptyOperation("import-certificate",
                PathAddress.pathAddress("subsystem", "elytron").append("key-store", trustStoreName));
        importCertOp.get("alias").set(CLIENT_KEY_ALIAS);
        importCertOp.get("path").set(clientCertPath);
        importCertOp.get("trust-cacerts").set(trust);
        importCertOp.get("validate").set(validateCertificate);
        importCertOp.get(ModelDescriptionConstants.OPERATION_HEADERS).get("allow-resource-service-restart").set(true);
        return importCertOp;
    }

    private ModelNode getExportCertificateOp(File path, String relativeTo, String keyStoreName) {
        // /subsystem=elytron/key-store=test:export-certificate(path=, alias=, relative-to=, pem=)
        final ModelNode exportCertOp = createEmptyOperation("export-certificate",
                PathAddress.pathAddress("subsystem", "elytron").append("key-store", keyStoreName));
        exportCertOp.get("path").set(path.getPath());
        exportCertOp.get("alias").set(SERVER_KEY_ALIAS);
        if (relativeTo != null) {
            exportCertOp.get("relative-to").set(relativeTo);
        }
        exportCertOp.get("pem").set(Boolean.TRUE);
        exportCertOp.get(ModelDescriptionConstants.OPERATION_HEADERS).get("allow-resource-service-restart").set(true);
        return exportCertOp;
    }

    private ModelNode getSigningRequestOp(File path, String relativeTo, String keyStoreName) {
        final ModelNode exportCertOp = createEmptyOperation("generate-certificate-signing-request",
                PathAddress.pathAddress("subsystem", "elytron").append("key-store", keyStoreName));
        exportCertOp.get("path").set(path.getPath());
        exportCertOp.get("alias").set(SERVER_KEY_ALIAS);
        if (relativeTo != null) {
            exportCertOp.get("relative-to").set(relativeTo);
        }
        exportCertOp.get(ModelDescriptionConstants.OPERATION_HEADERS).get("allow-resource-service-restart").set(true);
        return exportCertOp;
    }

    private ModelNode getStoreKeystoreOp(String storeName) {
        // /subsystem=elytron/key-store=test:store
        final ModelNode storeKSOp = createEmptyOperation("store",
                PathAddress.pathAddress("subsystem", "elytron").append("key-store", storeName));
        storeKSOp.get(ModelDescriptionConstants.OPERATION_HEADERS).get("allow-resource-service-restart").set(true);
        return storeKSOp;
    }

    private ModelNode getAddSslContextOp(Set<String> protocols, String cipherSuites, String tls13CipherNames,
                                         String trustManagerName, String sslContextName, String keyManagerName) {
        // /subsystem=elytron/server-ssl-context=foobar:add(key-manager=foobar, trust-manager=, protocols=, cipher-suite-names=)
        final ModelNode addSslContextOp = createEmptyOperation("add",
                PathAddress.pathAddress("subsystem", "elytron").append("server-ssl-context", sslContextName));
        addSslContextOp.get("key-manager").set(keyManagerName);
        if (trustManagerName != null) {
            // mutual SSL
            addSslContextOp.get("trust-manager").set(trustManagerName);
            addSslContextOp.get("need-client-auth").set(Boolean.TRUE);
        }
        if (protocols != null && !protocols.isEmpty()) {
            for (String protocol : protocols) {
                addSslContextOp.get("protocols").add(protocol);
            }
        }
        if (cipherSuites != null && cipherSuites.length() > 0) {
            addSslContextOp.get("cipher-suite-filter").set(cipherSuites);
        }
        if (tls13CipherNames != null && tls13CipherNames.length() > 0) {
            addSslContextOp.get("cipher-suite-names").set(tls13CipherNames);
        }
        addSslContextOp.get(ModelDescriptionConstants.OPERATION_HEADERS).get("allow-resource-service-restart").set(true);
        return addSslContextOp;
    }

    public abstract static class Config extends FlatListPostInstallConfig {
        public static final String PROTOCOL_ATTRIBUTE_KEY = "protocol";
        public static final String CIPHER_SUITES_ATTRIBUTE_KEY = "cipherSuites";
        public static final String CIPHER_NAMES_TLS13_ATTRIBUTE_KEY = "tls13CipherNames";
        public static final String KEYSTORE_PATH_ATTRIBUTE_KEY = "keyStorePath";
        public static final String MUTUAL_ATTRIBUTE_KEY = "mutual";
        public static final String TRUSTSTORE_PATH_ATTRIBUTE_KEY = "trustStorePath";
        public static final String DO_GENERATE_STORE_ATTRIBUTE_KEY = "doGenerateStore";
        public static final String DN_ATTRIBUTE_KEY = "dn";
        public static final String VALIDITY_ATTRIBUTE_KEY = "validity";
        public static final String CLIENT_CERT_PATH_ATTRIBUTE_KEY = "clientCertPath";
        public static final String VALIDATE_CERTIFICATE_ATTRIBUTE_KEY = "validateCertificate";
        public static final String KEYSTORE_PASSWORD_VARIABLE_KEY = "keystorePassword";
        public static final String KEYSTORE_PASSWORD_DESC = "Keystore password";
        public static final String TRUSTSTORE_PASSWORD_VARIABLE_KEY = "truststorePassword";
        public static final String TRUSTSTORE_PASSWORD_DESC = "Truststore password";

        private Set<String> protocols;
        private String cipherSuites;
        private String keyStorePath;
        private String keyStorePassword;
        private boolean mutual;
        private String trustStorePath;
        private String trustStorePassword;
        private boolean doGenerateStore;
        private String dn;
        private String validity;
        private String clientCertPath;
        private boolean validateCertificate;

        private boolean keystoreGeneratedForStandalone = false;
        private boolean keystoreGeneratedForDomain = false;
        private boolean clientCertImportedForStandalone = false;
        private boolean clientCertImportedForDomain = false;
        private String tls13cipherNames;

        public Config() {
            // no-op for unmarshalling
        }

        public Config(Set<String> protocols, String cipherSuites, String tls13cipherNames, String keyStorePath,
                      String keyStorePassword, boolean mutual, String trustStorePath, String trustStorePassword,
                      boolean doGenerateStore, String dn, String validity, String clientCertPath, boolean validateCertificate) {
            this.protocols = protocols;
            this.cipherSuites = cipherSuites;
            this.tls13cipherNames = tls13cipherNames;
            this.keyStorePath = keyStorePath;
            this.keyStorePassword = keyStorePassword;
            this.mutual = mutual;
            this.trustStorePath = trustStorePath;
            this.trustStorePassword = trustStorePassword;
            this.doGenerateStore = doGenerateStore;
            this.dn = dn;
            this.validity = validity;
            this.clientCertPath = clientCertPath;
            this.validateCertificate = validateCertificate;
        }

        public Set<String> getProtocols() {
            return protocols;
        }

        public String getCipherSuites() {
            return cipherSuites;
        }

        public String getTls13cipherNames() {
            return tls13cipherNames;
        }

        public void setTls13cipherNames(String tls13cipherNames) {
            this.tls13cipherNames = tls13cipherNames;
        }

        public String getKeyStorePath() {
            return keyStorePath;
        }

        public String getKeyStorePassword() {
            return keyStorePassword;
        }

        public boolean isMutual() {
            return mutual;
        }

        public String getTrustStorePath() {
            return trustStorePath;
        }

        public String getTrustStorePassword() {
            return trustStorePassword;
        }

        public boolean isDoGenerateStore() {
            return doGenerateStore;
        }

        public String getDn() {
            return dn;
        }

        public String getValidity() {
            return validity;
        }

        public String getClientCertPath() {
            return clientCertPath;
        }

        public boolean isValidateCertificate() {
            return validateCertificate;
        }

        public boolean isKeystoreGeneratedForStandalone() {
            return keystoreGeneratedForStandalone;
        }

        public void setKeystoreGeneratedForStandalone(boolean keystoreGeneratedForStandalone) {
            this.keystoreGeneratedForStandalone = keystoreGeneratedForStandalone;
        }

        public boolean isKeystoreGeneratedForDomain() {
            return keystoreGeneratedForDomain;
        }

        public void setKeystoreGeneratedForDomain(boolean keystoreGeneratedForDomain) {
            this.keystoreGeneratedForDomain = keystoreGeneratedForDomain;
        }

        public boolean isClientCertImportedForStandalone() {
            return clientCertImportedForStandalone;
        }

        public void setClientCertImportedForStandalone(boolean clientCertImportedForStandalone) {
            this.clientCertImportedForStandalone = clientCertImportedForStandalone;
        }

        public boolean isClientCertImportedForDomain() {
            return clientCertImportedForDomain;
        }

        public void setClientCertImportedForDomain(boolean clientCertImportedForDomain) {
            this.clientCertImportedForDomain = clientCertImportedForDomain;
        }

        @Override
        protected Map<String, String> listAttributes() {
            final HashMap<String, String> attrs = new HashMap<>();
            attrs.put(PROTOCOL_ATTRIBUTE_KEY, protocols!=null?StringUtils.join(protocols,","):null);
            attrs.put(CIPHER_SUITES_ATTRIBUTE_KEY, cipherSuites);
            attrs.put(CIPHER_NAMES_TLS13_ATTRIBUTE_KEY, tls13cipherNames);
            attrs.put(KEYSTORE_PATH_ATTRIBUTE_KEY, keyStorePath);
            attrs.put(MUTUAL_ATTRIBUTE_KEY, Boolean.toString(mutual));
            attrs.put(TRUSTSTORE_PATH_ATTRIBUTE_KEY, trustStorePath);
            attrs.put(DO_GENERATE_STORE_ATTRIBUTE_KEY, Boolean.toString(doGenerateStore));
            attrs.put(DN_ATTRIBUTE_KEY, dn);
            attrs.put(VALIDITY_ATTRIBUTE_KEY, validity);
            attrs.put(CLIENT_CERT_PATH_ATTRIBUTE_KEY, clientCertPath);
            attrs.put(VALIDATE_CERTIFICATE_ATTRIBUTE_KEY, Boolean.toString(validateCertificate));
            return attrs;
        }

        @Override
        protected Set<String> listVariables() {
            final HashSet<String> vars = new HashSet<>();
            if (keyStorePath != null && !keyStorePath.isEmpty()) {
                vars.add(KEYSTORE_PASSWORD_VARIABLE_KEY);
            }
            if (trustStorePath != null && !trustStorePath.isEmpty()) {
                vars.add(TRUSTSTORE_PASSWORD_VARIABLE_KEY);
            }
            return vars;
        }

        @Override
        protected void acceptAttributes(Map<String, String> attributes, BiFunction<String, String, String> variableResolver) throws AutomaticInstallationParsingException {
            protocols = attributes.get(PROTOCOL_ATTRIBUTE_KEY)!=null?Set.of(StringUtils.split(attributes.get(PROTOCOL_ATTRIBUTE_KEY), ',')):null;
            cipherSuites = attributes.get(CIPHER_SUITES_ATTRIBUTE_KEY);
            tls13cipherNames = attributes.get(CIPHER_NAMES_TLS13_ATTRIBUTE_KEY);
            keyStorePath = attributes.get(KEYSTORE_PATH_ATTRIBUTE_KEY);
            mutual = Boolean.parseBoolean(attributes.get(MUTUAL_ATTRIBUTE_KEY));
            trustStorePath = attributes.get(TRUSTSTORE_PATH_ATTRIBUTE_KEY);
            doGenerateStore = Boolean.parseBoolean(attributes.get(DO_GENERATE_STORE_ATTRIBUTE_KEY));
            dn = attributes.get(DN_ATTRIBUTE_KEY);
            validity = attributes.get(VALIDITY_ATTRIBUTE_KEY);
            clientCertPath = attributes.get(CLIENT_CERT_PATH_ATTRIBUTE_KEY);
            validateCertificate = Boolean.parseBoolean(attributes.get(VALIDATE_CERTIFICATE_ATTRIBUTE_KEY));
            if (keyStorePath != null && !keyStorePath.isEmpty()) {
                keyStorePassword = variableResolver.apply(KEYSTORE_PASSWORD_VARIABLE_KEY, KEYSTORE_PASSWORD_DESC);
            }
            if (trustStorePath != null && !trustStorePath.isEmpty()) {
                trustStorePassword = variableResolver.apply(TRUSTSTORE_PASSWORD_VARIABLE_KEY, TRUSTSTORE_PASSWORD_DESC);
            }
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Config config = (Config) o;
            return mutual == config.mutual && doGenerateStore == config.doGenerateStore && validateCertificate == config.validateCertificate && keystoreGeneratedForStandalone == config.keystoreGeneratedForStandalone && keystoreGeneratedForDomain == config.keystoreGeneratedForDomain && clientCertImportedForStandalone == config.clientCertImportedForStandalone && clientCertImportedForDomain == config.clientCertImportedForDomain && Objects.equals(protocols, config.protocols) && Objects.equals(cipherSuites, config.cipherSuites) && Objects.equals(keyStorePath, config.keyStorePath) && Objects.equals(keyStorePassword, config.keyStorePassword) && Objects.equals(trustStorePath, config.trustStorePath) && Objects.equals(trustStorePassword, config.trustStorePassword) && Objects.equals(dn, config.dn) && Objects.equals(validity, config.validity) && Objects.equals(clientCertPath, config.clientCertPath);
        }

        @Override
        public int hashCode() {
            return Objects.hash(protocols, cipherSuites, keyStorePath, keyStorePassword, mutual, trustStorePath, trustStorePassword, doGenerateStore, dn, validity, clientCertPath, validateCertificate, keystoreGeneratedForStandalone, keystoreGeneratedForDomain, clientCertImportedForStandalone, clientCertImportedForDomain);
        }

        @Override
        public String toString() {
            return "Config{" +
                    "protocols=" + protocols +
                    ", cipherSuites='" + cipherSuites + '\'' +
                    ", keyStorePath='" + keyStorePath + '\'' +
                    ", keyStorePassword='" + keyStorePassword + '\'' +
                    ", mutual=" + mutual +
                    ", trustStorePath='" + trustStorePath + '\'' +
                    ", trustStorePassword='" + trustStorePassword + '\'' +
                    ", doGenerateStore=" + doGenerateStore +
                    ", dn='" + dn + '\'' +
                    ", validity='" + validity + '\'' +
                    ", clientCertPath='" + clientCertPath + '\'' +
                    ", validateCertificate=" + validateCertificate +
                    ", keystoreGeneratedForStandalone=" + keystoreGeneratedForStandalone +
                    ", keystoreGeneratedForDomain=" + keystoreGeneratedForDomain +
                    ", clientCertImportedForStandalone=" + clientCertImportedForStandalone +
                    ", clientCertImportedForDomain=" + clientCertImportedForDomain +
                    ", tls13cipherNames='" + tls13cipherNames + '\'' +
                    '}';
        }
    }
}
