All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openeuler.tomcat.GMJSSEImplementation Maven / Gradle / Ivy

/*
 * Copyright (c) 2021, Huawei Technologies Co., Ltd. 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.  Huawei designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Huawei 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 visit https://gitee.com/openeuler/bgmprovider if you need additional
 * information or have any questions.
 */

package org.openeuler.tomcat;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.apache.tomcat.util.net.SSLUtil;
import org.apache.tomcat.util.net.jsse.JSSEImplementation;

import java.lang.reflect.Field;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * JSSEImplementation support GMTLS
 */
public class GMJSSEImplementation extends JSSEImplementation {

    private static final Log log = LogFactory.getLog(GMJSSEImplementation.class);
    // GM TLS protocol version
    private static final String GM_PROTOCOL = "GMTLS";

    // cipher separator
    private static final String SEPARATOR = ":|,| ";

    // 'high' encryption cipher suites
    private static final String HIGH = "HIGH";

    /**
     * If ! is used then the ciphers are permanently deleted from the list.
     * The ciphers deleted can never reappear in the list, even if they are explicitly stated.
     */
    private static final String EXCLUDE = "!";

    /**
     * If - is used then the ciphers are deleted from the list,
     * but some or all of the ciphers can be added again by later
     * options.
     */
    private static final String DELETE = "-";

    // cipher expression represents all cipher
    private static final String ALL = "ALL";
    private static final Set GM_CIPHERS_NAME_SET = new HashSet<>();
    private static final Map> ALIAS_MAP = new HashMap<>();

    /* Set the provider class through the system property 'tomcat.gmtls.providers',
     * and multiple providers are separated by commas
     */
    private static final String gmtlsProviders = System.getProperty("tomcat.gmtls.providers", "org.openeuler.BGMProvider");

    private Set explicitlyRequestedProtocols;

    static {
        initProvider();
        initGMCiphersNameSetAndAliasMap();
    }

    enum GMCipherGroup {
        GM_ECC,
        GM_ECDHE
    }

    enum GMCipher {
        ECC_SM4_CBC_SM3("ECC_SM4_CBC_SM3", "ECC_SM4_SM3", GMCipherGroup.GM_ECC),
        ECDHE_SM4_CBC_SM3("ECDHE_SM4_CBC_SM3", "ECDHE_SM4_SM3", GMCipherGroup.GM_ECDHE),
        ECC_SM4_GCM_SM3("ECC_SM4_GCM_SM3", GMCipherGroup.GM_ECC),
        ECDHE_SM4_GCM_SM3("ECDHE_SM4_GCM_SM3", GMCipherGroup.GM_ECDHE);
        final String cipherName;
        final String aliasCipherName;
        final GMCipherGroup cipherGroup;

        GMCipher(String cipherName, GMCipherGroup cipherGroup) {
            this(cipherName, "", cipherGroup);
        }

        GMCipher(String cipherName, String aliasCipherName, GMCipherGroup cipherGroup) {
            this.cipherName = cipherName;
            this.aliasCipherName = aliasCipherName;
            this.cipherGroup = cipherGroup;
        }
    }

    private static void initProvider() {
        String[] providerClasses = Arrays.stream(gmtlsProviders.split(",")).map(s -> s.trim()).toArray(String[]::new);
        for (int i = providerClasses.length - 1; i >= 0; i--) {
            String clazz = providerClasses[i];
            if (clazz == null || clazz.trim().isEmpty()) {
                continue;
            }
            try {
                ClassLoader classLoader = GMJSSEImplementation.class.getClassLoader();
                Class providerClass = classLoader.loadClass(clazz);
                Object bgmProvider = providerClass.getDeclaredConstructor().newInstance();
                Security.insertProviderAt((Provider) bgmProvider, 1);
            } catch (Exception ex) {
                log.warn("Failed to initialize provider: " + clazz, ex);
            }
        }
    }

    private static void initGMCiphersNameSetAndAliasMap() {
        GMCipher[] gmCiphers = GMCipher.values();
        for (GMCipher gmCipher : gmCiphers) {
            GM_CIPHERS_NAME_SET.add(gmCipher.cipherName);

            // cipherName
            List aliasCiphers = ALIAS_MAP.computeIfAbsent(gmCipher.cipherName, k -> new ArrayList<>());
            aliasCiphers.add(gmCipher.cipherName);

            // aliasCipherName
            if (!gmCipher.aliasCipherName.isEmpty()) {
                aliasCiphers = ALIAS_MAP.computeIfAbsent(gmCipher.aliasCipherName, k -> new ArrayList<>());
                aliasCiphers.add(gmCipher.cipherName);
            }

            // cipherGroup
            aliasCiphers = ALIAS_MAP.computeIfAbsent(gmCipher.cipherGroup.name(),
                    k -> new ArrayList<>());
            aliasCiphers.add(gmCipher.cipherName);
        }
    }

    public GMJSSEImplementation() {
        super();
    }

    @Override
    public SSLUtil getSSLUtil(SSLHostConfigCertificate certificate) {
        SSLHostConfig sslHostConfig = certificate.getSSLHostConfig();
        initGMProtocol(sslHostConfig);
        initGMCipherSuites(sslHostConfig);
        return new GMJSSEUtil(certificate);
    }

    /**
     * init GM protocol
     */
    private void initGMProtocol(SSLHostConfig sslHostConfig) {
        if (needAddGMProtocol(sslHostConfig)) {
            sslHostConfig.getProtocols().add(GM_PROTOCOL);
        }
    }

    @SuppressWarnings(value = "unchecked")
    private Set getExplicitlyRequestedProtocol(SSLHostConfig sslHostConfig) {
        if (explicitlyRequestedProtocols != null) {
            return explicitlyRequestedProtocols;
        }
        Field field;
        try {
            field = sslHostConfig.getClass().getDeclaredField("explicitlyRequestedProtocols");
            field.setAccessible(true);
            explicitlyRequestedProtocols = (Set) field.get(sslHostConfig);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            log.warn("The explicitlyRequestedProtocols field is not defined in the SSLHostConfig class.");
            explicitlyRequestedProtocols = new HashSet<>();
        }
        return explicitlyRequestedProtocols;
    }

    /**
     * If explicitlyRequestedProtocol is empty, it means that SSLHostConfig does not set the protocols property
     * or the Connector does not set the sslEnabledProtocols property explicitly.
     * 

* The SSLHostConfig#protocols are set to SSLHostConfig#SSL_PROTO_ALL_SET and SSLHostConfig#SSL_PROTO_ALL_SET * does not contain GMTLS, we need to add GMTLS protocol. */ private boolean needAddGMProtocol(SSLHostConfig sslHostConfig) { Set explicitlyRequestedProtocol = getExplicitlyRequestedProtocol(sslHostConfig); return explicitlyRequestedProtocol.isEmpty(); } /** * Init GM cipher suites */ private void initGMCipherSuites(SSLHostConfig sslHostConfig) { String ciphers = sslHostConfig.getCiphers(); Set gmCiphers = parseCiphers(ciphers); sslHostConfig.getJsseCipherNames().addAll(gmCiphers); log.info("gmCiphers = " + gmCiphers); } /** * Parse cipher expression */ private Set parseCiphers(String expression) { String[] elements = expression.split(SEPARATOR); Set gmCiphers = new HashSet<>(); Set removedCiphers = new HashSet<>(); for (String element : elements) { parseCipher(element, gmCiphers, removedCiphers); } gmCiphers.removeAll(removedCiphers); log.info("Remove cipher suites : " + removedCiphers); return gmCiphers; } /** * Parse cipher expression, refer to OpenSSLCipherConfigurationParser#parse. * Only support DELETE and EXCLUDE now , TO_END and END are not supported. * For example : *

* ECC_SM4_CBC_SM3 * ALL:-ECC_SM4_CBC_SM3 * ALL:!ECC_SM4_CBC_SM3 * GM_ECC:GM_ECDH * ALL:!GM_ECC * ALL:!GM_ECDHE * * @see org.apache.tomcat.util.net.openssl.ciphers.OpenSSLCipherConfigurationParser#parse(String) */ private void parseCipher(String element, Set gmCiphers, Set removedCiphers) { if (ALIAS_MAP.containsKey(element)) { List aliasCiphers = ALIAS_MAP.get(element); gmCiphers.addAll(aliasCiphers); log.info("Add cipher suites : " + aliasCiphers); } else if (element.equals(HIGH) || element.equals(ALL)) { gmCiphers.addAll(GM_CIPHERS_NAME_SET); log.info("Add cipher suites :" + GM_CIPHERS_NAME_SET); } else if (element.startsWith(DELETE)) { String alias = element.substring(1); if (ALIAS_MAP.containsKey(alias)) { List aliasCiphers = ALIAS_MAP.get(alias); aliasCiphers.forEach(gmCiphers::remove); log.info("Remove cipher suites : " + aliasCiphers); } } else if (element.startsWith(EXCLUDE)) { String alias = element.substring(1); if (ALIAS_MAP.containsKey(alias)) { List aliasCiphers = ALIAS_MAP.get(alias); removedCiphers.addAll(aliasCiphers); } } // else skip } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy