This is an automated email from the ASF dual-hosted git repository.
spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 82c23495f feat: add GM support (#8389)
82c23495f is described below
commit 82c23495fc0de7814b8b81138487eebdfa09cef9
Author: 罗泽轩 <[email protected]>
AuthorDate: Thu Nov 24 16:13:28 2022 +0800
feat: add GM support (#8389)
Co-authored-by: Sylvia <[email protected]>
---
.github/workflows/gm.yml | 114 ++++++++++++++++++++++++++
apisix/plugins/gm.lua | 175 +++++++++++++++++++++++++++++++++++++++
conf/config-default.yaml | 1 +
docs/zh/latest/config.json | 3 +-
docs/zh/latest/plugins/gm.md | 191 +++++++++++++++++++++++++++++++++++++++++++
t/certs/client_enc.crt | 12 +++
t/certs/client_enc.key | 5 ++
t/certs/client_sign.crt | 12 +++
t/certs/client_sign.key | 5 ++
t/certs/gm_ca.crt | 26 ++++++
t/certs/server_enc.crt | 12 +++
t/certs/server_enc.key | 5 ++
t/certs/server_sign.crt | 12 +++
t/certs/server_sign.key | 5 ++
t/gm/gm.t | 170 ++++++++++++++++++++++++++++++++++++++
15 files changed, 747 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/gm.yml b/.github/workflows/gm.yml
new file mode 100644
index 000000000..cb1444e0d
--- /dev/null
+++ b/.github/workflows/gm.yml
@@ -0,0 +1,114 @@
+name: CI GM
+
+on:
+ push:
+ branches: [master]
+ paths-ignore:
+ - 'docs/**'
+ - '**/*.md'
+ pull_request:
+ branches: [master]
+ paths-ignore:
+ - 'docs/**'
+ - '**/*.md'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/master' &&
github.run_number || github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ platform:
+ - ubuntu-20.04
+ os_name:
+ - linux_openresty
+ test_dir:
+ # TODO: cover all tests by adding a CI cron job
+ - t/gm
+
+ runs-on: ${{ matrix.platform }}
+ timeout-minutes: 90
+ env:
+ SERVER_NAME: ${{ matrix.os_name }}
+ OPENRESTY_VERSION: default
+
+ steps:
+ - name: Check out code
+ uses: actions/[email protected]
+ with:
+ submodules: recursive
+
+ - name: Cache deps
+ uses: actions/cache@v3
+ env:
+ cache-name: cache-deps
+ with:
+ path: deps
+ key: ${{ runner.os }}-${{ env.cache-name }}-${{ matrix.os_name
}}-${{ hashFiles('rockspec/apisix-master-0.rockspec') }}
+
+ - name: Cache Tongsuo compilation
+ id: cache-tongsuo
+ uses: actions/cache@v3
+ env:
+ cache-name: cache-tongsuo
+ with:
+ path: ./tongsuo
+ # TODO: use a fixed release once they have created one.
+ # See https://github.com/Tongsuo-Project/Tongsuo/issues/318
+ key: ${{ runner.os }}-${{ env.cache-name }}-${{ matrix.os_name
}}-tongsuo-ver
+
+ - name: Compile SSL lib
+ shell: bash
+ if: steps.cache-tongsuo.outputs.cache-hit != 'true'
+ run: |
+ git clone https://github.com/api7/tongsuo --depth 1
+ pushd tongsuo
+ # build binary
+ ./config enable-ntls -static
+ make -j2
+ mv apps/openssl apps/static-openssl
+ ./config shared enable-ntls -g --prefix=/usr/local/tongsuo
+ make -j2
+ popd
+
+ - name: Install SSL lib
+ run: |
+ pushd tongsuo
+ sudo make install_sw
+ sudo cp apps/static-openssl /usr/local/tongsuo/bin/openssl
+ export PATH=/usr/local/tongsuo/bin:$PATH
+ openssl version
+ popd
+
+ - name: Linux launch common services
+ run: |
+ make ci-env-up project_compose_ci=ci/pod/docker-compose.common.yml
+
+ - name: Linux Before install
+ run: |
+ sudo ./ci/${{ matrix.os_name }}_runner.sh before_install
+
+ - name: Linux Do install
+ run: |
+ export OR_PREFIX=/usr/local/openresty-debug
+ export openssl_prefix=/usr/local/tongsuo
+ export zlib_prefix=$OR_PREFIX/zlib
+ export pcre_prefix=$OR_PREFIX/pcre
+
+ export cc_opt="-DNGX_LUA_ABORT_AT_PANIC -I${zlib_prefix}/include
-I${pcre_prefix}/include -I${openssl_prefix}/include"
+ export ld_opt="-L${zlib_prefix}/lib -L${pcre_prefix}/lib
-L${openssl_prefix}/lib64
-Wl,-rpath,${zlib_prefix}/lib:${pcre_prefix}/lib:${openssl_prefix}/lib64"
+ sudo --preserve-env=OPENRESTY_VERSION \
+ --preserve-env=cc_opt,ld_opt \
+ ./ci/${{ matrix.os_name }}_runner.sh do_install
+
+ - name: Linux Script
+ env:
+ TEST_FILE_SUB_DIR: ${{ matrix.test_dir }}
+ run: |
+ sudo -E ./ci/${{ matrix.os_name }}_runner.sh script
diff --git a/apisix/plugins/gm.lua b/apisix/plugins/gm.lua
new file mode 100644
index 000000000..929dac6f0
--- /dev/null
+++ b/apisix/plugins/gm.lua
@@ -0,0 +1,175 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you 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.
+
+-- local common libs
+local require = require
+local pcall = pcall
+local ffi = require("ffi")
+local C = ffi.C
+local get_request = require("resty.core.base").get_request
+local core = require("apisix.core")
+local radixtree_sni = require("apisix.ssl.router.radixtree_sni")
+local apisix_ssl = require("apisix.ssl")
+local _, ssl = pcall(require, "resty.apisix.ssl")
+local error = error
+
+
+ffi.cdef[[
+unsigned long Tongsuo_version_num(void)
+]]
+
+
+-- local function
+local function set_pem_ssl_key(sni, enc_cert, enc_pkey, sign_cert, sign_pkey)
+ local r = get_request()
+ if r == nil then
+ return false, "no request found"
+ end
+
+ local parsed_enc_cert, err = apisix_ssl.fetch_cert(sni, enc_cert)
+ if not parsed_enc_cert then
+ return false, "failed to parse enc PEM cert: " .. err
+ end
+
+ local parsed_sign_cert, err = apisix_ssl.fetch_cert(sni, sign_cert)
+ if not parsed_sign_cert then
+ return false, "failed to parse sign PEM cert: " .. err
+ end
+
+ local ok, err = ssl.set_gm_cert(parsed_enc_cert, parsed_sign_cert)
+ if not ok then
+ return false, "failed to set PEM cert: " .. err
+ end
+
+ local parsed_enc_pkey, err = apisix_ssl.fetch_pkey(sni, enc_pkey)
+ if not parsed_enc_pkey then
+ return false, "failed to parse enc PEM priv key: " .. err
+ end
+
+ local parsed_sign_pkey, err = apisix_ssl.fetch_pkey(sni, sign_pkey)
+ if not parsed_sign_pkey then
+ return false, "failed to parse sign PEM priv key: " .. err
+ end
+
+ ok, err = ssl.set_gm_priv_key(parsed_enc_pkey, parsed_sign_pkey)
+ if not ok then
+ return false, "failed to set PEM priv key: " .. err
+ end
+
+ return true
+end
+
+
+local original_set_cert_and_key
+local function set_cert_and_key(sni, value)
+ if value.gm then
+ -- process as GM certificate
+ -- For GM dual certificate, the `cert` and `key` will be encryption
cert/key.
+ -- The first item in `certs` and `keys` will be sign cert/key.
+ local enc_cert = value.cert
+ local enc_pkey = value.key
+ local sign_cert = value.certs[1]
+ local sign_pkey = value.keys[1]
+ return set_pem_ssl_key(sni, enc_cert, enc_pkey, sign_cert, sign_pkey)
+ end
+ return original_set_cert_and_key(sni, value)
+end
+
+
+local original_check_ssl_conf
+local function check_ssl_conf(in_dp, conf)
+ if conf.gm then
+ -- process as GM certificate
+ -- For GM dual certificate, the `cert` and `key` will be encryption
cert/key.
+ -- The first item in `certs` and `keys` will be sign cert/key.
+ local ok, err = original_check_ssl_conf(in_dp, conf)
+ -- check cert/key first in the original method
+ if not ok then
+ return nil, err
+ end
+
+ -- Currently, APISIX doesn't check the cert type (ECDSA / RSA). So we
skip the
+ -- check for now in this plugin.
+ local num_certs = conf.certs and #conf.certs or 0
+ local num_keys = conf.keys and #conf.keys or 0
+ if num_certs ~= 1 or num_keys ~= 1 then
+ return nil, "sign cert/key are required"
+ end
+ return true
+ end
+ return original_check_ssl_conf(in_dp, conf)
+end
+
+
+-- module define
+local plugin_name = "gm"
+
+-- plugin schema
+local plugin_schema = {
+ type = "object",
+ properties = {
+ },
+}
+
+local _M = {
+ version = 0.1, -- plugin version
+ priority = -43,
+ name = plugin_name, -- plugin name
+ schema = plugin_schema, -- plugin schema
+}
+
+
+function _M.init()
+ if not pcall(function () return C.Tongsuo_version_num end) then
+ error("need to build Tongsuo
(https://github.com/Tongsuo-Project/Tongsuo) " ..
+ "into the APISIX-Base")
+ end
+
+ ssl.enable_ntls()
+ original_set_cert_and_key = radixtree_sni.set_cert_and_key
+ radixtree_sni.set_cert_and_key = set_cert_and_key
+ original_check_ssl_conf = apisix_ssl.check_ssl_conf
+ apisix_ssl.check_ssl_conf = check_ssl_conf
+
+ if core.schema.ssl.properties.gm ~= nil then
+ error("Field 'gm' is occupied")
+ end
+
+ -- inject a mark to distinguish GM certificate
+ core.schema.ssl.properties.gm = {
+ type = "boolean"
+ }
+end
+
+
+function _M.destroy()
+ ssl.disable_ntls()
+ radixtree_sni.set_cert_and_key = original_set_cert_and_key
+ apisix_ssl.check_ssl_conf = original_check_ssl_conf
+ core.schema.ssl.properties.gm = nil
+end
+
+-- module interface for schema check
+-- @param `conf` user defined conf data
+-- @param `schema_type` defined in `apisix/core/schema.lua`
+-- @return <boolean>
+function _M.check_schema(conf, schema_type)
+ return core.schema.check(plugin_schema, conf)
+end
+
+
+return _M
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index 6e714b577..6f9de38bd 100755
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -464,6 +464,7 @@ plugins: # plugin list (sorted by
priority)
#- log-rotate # priority: 100
# <- recommend to use priority (0, 100) for your custom plugins
- example-plugin # priority: 0
+ #- gm # priority: -43
- aws-lambda # priority: -1899
- azure-functions # priority: -1900
- openwhisk # priority: -1901
diff --git a/docs/zh/latest/config.json b/docs/zh/latest/config.json
index 2657f45ce..8f2f3efe8 100644
--- a/docs/zh/latest/config.json
+++ b/docs/zh/latest/config.json
@@ -99,7 +99,8 @@
"plugins/referer-restriction",
"plugins/consumer-restriction",
"plugins/csrf",
- "plugins/public-api"
+ "plugins/public-api",
+ "plugins/gm"
]
},
{
diff --git a/docs/zh/latest/plugins/gm.md b/docs/zh/latest/plugins/gm.md
new file mode 100644
index 000000000..75dd8e072
--- /dev/null
+++ b/docs/zh/latest/plugins/gm.md
@@ -0,0 +1,191 @@
+---
+title: GM
+keywords:
+ - APISIX
+ - Plugin
+ - GM
+description: 本文介绍了关于 Apache APISIX gm 插件的基本信息及使用方法。
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## 描述
+
+`gm` 插件能启用国密相关的功能。目前支持通过该插件动态配置国密双证书。
+
+:::note 相关介绍
+国密就是国产化的密码算法。在我们日常开发过程中会接触到各种各样的密码算法,如 RSA、SHA256
等等。为了达到更高的安全等级,许多大公司和国家会制定自己的密码算法。国密就是这样一组由中国国家密码管理局制定的密码算法。在国际形势越发复杂多变的今天,密码算法的国产化替代,在一些领域已经成为了一股势不可挡的潮流。
+:::
+
+## 启用插件
+
+**该插件要求 Apache APISIX 运行在编译了 Tongsuo 的 APISIX-Base 上。**
+
+首先,我们需要安装 Tongsuo(此处我们选择编译出 Tongsuo 的动态链接库):
+
+```
+# TODO: use a fixed release once they have created one.
+# See https://github.com/Tongsuo-Project/Tongsuo/issues/318
+git clone https://github.com/api7/tongsuo --depth 1
+pushd tongsuo
+./config shared enable-ntls -g --prefix=/usr/local/tongsuo
+make -j2
+sudo make install_sw
+```
+
+其次,我们需要构建 APISIX-Base,让它使用 Tongsuo 作为 SSL 库:
+
+```
+export OR_PREFIX=/usr/local/openresty
+export openssl_prefix=/usr/local/tongsuo
+export zlib_prefix=$OR_PREFIX/zlib
+export pcre_prefix=$OR_PREFIX/pcre
+
+export cc_opt="-DNGX_LUA_ABORT_AT_PANIC -I${zlib_prefix}/include
-I${pcre_prefix}/include -I${openssl_prefix}/include"
+export ld_opt="-L${zlib_prefix}/lib -L${pcre_prefix}/lib
-L${openssl_prefix}/lib64
-Wl,-rpath,${zlib_prefix}/lib:${pcre_prefix}/lib:${openssl_prefix}/lib64"
+./build-apisix-base.sh
+```
+
+该插件默认是禁用状态,你需要将其添加到配置文件(`./conf/config.yaml`)中才可以启用它:
+
+```yaml
+plugins:
+ - ...
+ - gm
+```
+
+由于 APISIX 的默认 cipher 中不包含国密 cipher,所以我们还需要在配置文件(`./conf/config.yaml`)中设置
cipher:
+
+```yaml
+apisix:
+ ...
+ ssl:
+ ...
+ # 可按实际情况调整。错误的 cipher 会导致 “no shared cipher” 或 “no ciphers available” 报错。
+ ssl_ciphers: HIGH:!aNULL:!MD5
+
+```
+
+配置完成后,重新加载 APISIX,此时 APISIX 将会启用国密相关的逻辑。
+
+## 测试插件
+
+在测试插件之前,我们需要准备好国密双证书。Tongsuo 提供了生成[SM2
双证书](https://www.yuque.com/tsdoc/ts/sulazb)的教程。
+
+在下面的例子中,我们将用到如下的证书:
+
+```
+# 客户端加密证书和密钥
+t/certs/client_enc.crt
+t/certs/client_enc.key
+# 客户端签名证书和密钥
+t/certs/client_sign.crt
+t/certs/client_sign.key
+# CA 和中间 CA 打包在一起的文件,用于设置受信任的 CA
+t/certs/gm_ca.crt
+# 服务端加密证书和密钥
+t/certs/server_enc.crt
+t/certs/server_enc.key
+# 服务端签名证书和密钥
+t/certs/server_sign.crt
+t/certs/server_sign.key
+```
+
+此外,我们还需要准备 Tongsuo 命令行工具。
+
+```
+./config enable-ntls -static
+make -j2
+# 生成的命令行工具在 apps 目录下
+mv apps/openssl ..
+```
+
+你也可以采用非静态编译的方式,不过就需要根据具体环境,自己解决动态链接库的路径问题了。
+
+以下示例展示了如何在指定域名中启用 `gm` 插件:
+
+创建对应的 SSL 对象:
+
+```python
+#!/usr/bin/env python
+# coding: utf-8
+
+import sys
+# sudo pip install requests
+import requests
+
+if len(sys.argv) <= 3:
+ print("bad argument")
+ sys.exit(1)
+with open(sys.argv[1]) as f:
+ enc_cert = f.read()
+with open(sys.argv[2]) as f:
+ enc_key = f.read()
+with open(sys.argv[3]) as f:
+ sign_cert = f.read()
+with open(sys.argv[4]) as f:
+ sign_key = f.read()
+api_key = "edd1c9f034335f136f87ad84b625c8f1"
+resp = requests.put("http://127.0.0.1:9180/apisix/admin/ssls/1", json={
+ "cert": enc_cert,
+ "key": enc_key,
+ "certs": [sign_cert],
+ "keys": [sign_key],
+ "gm": True,
+ "snis": ["localhost"],
+}, headers={
+ "X-API-KEY": api_key,
+})
+print(resp.status_code)
+print(resp.text)
+```
+
+将上面的脚本保存为 `./create_gm_ssl.py`,运行:
+
+```shell
+./create_gm_ssl.py t/certs/server_enc.crt t/certs/server_enc.key
t/certs/server_sign.crt t/certs/server_sign.key
+```
+
+输出结果如下所示:
+
+```
+200
+{"key":"\/apisix\/ssls\/1","value":{"keys":["Yn...
+```
+
+完成上述准备后,可以使用如下命令测试插件是否启用成功:
+
+```shell
+./openssl s_client -connect localhost:9443 -servername localhost -cipher
ECDHE-SM2-WITH-SM4-SM3 -enable_ntls -ntls -verifyCAfile t/certs/gm_ca.crt
-sign_cert t/certs/client_sign.crt -sign_key t/certs/client_sign.key -enc_cert
t/certs/client_enc.crt -enc_key t/certs/client_enc.key
+```
+
+这里 `./openssl` 是前面得到的 Tongsuo 命令行工具。9443 是 APISIX 默认的 HTTPS 端口。
+
+如果一切正常,可以看到连接建立了起来,并输出如下信息:
+
+```
+...
+New, NTLSv1.1, Cipher is ECDHE-SM2-SM4-CBC-SM3
+...
+```
+
+## 禁用插件
+
+如果不再使用此插件,可将 `gm` 插件从 `./conf/config.yaml` 配置文件中移除,然后重启 APISIX
或者通过插件热加载的接口触发插件的卸载。
diff --git a/t/certs/client_enc.crt b/t/certs/client_enc.crt
new file mode 100644
index 000000000..4e5bc78bc
--- /dev/null
+++ b/t/certs/client_enc.crt
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIB2TCCAX6gAwIBAgIBBTAKBggqgRzPVQGDdTBFMQswCQYDVQQGEwJBQTELMAkG
+A1UECAwCQkIxCzAJBgNVBAoMAkNDMQswCQYDVQQLDAJERDEPMA0GA1UEAwwGc3Vi
+IGNhMB4XDTIyMTEwMjAzMTkzNloXDTMyMTAzMDAzMTkzNlowSTELMAkGA1UEBhMC
+QUExCzAJBgNVBAgMAkJCMQswCQYDVQQKDAJDQzELMAkGA1UECwwCREQxEzARBgNV
+BAMMCmNsaWVudCBlbmMwWjAUBggqgRzPVQGCLQYIKoEcz1UBgi0DQgAEYYuPPz5e
+0QMSGPeBfVbK02GwYhSieSCuc12WsNw+ZQEiaN3NJ2Mh0EAH95eWVutKAeMwKwQZ
+q7QgnSoo3io8hKNaMFgwCQYDVR0TBAIwADALBgNVHQ8EBAMCAzgwHQYDVR0OBBYE
+FEL0AwvahirH+kdK5Poq+e0yhii1MB8GA1UdIwQYMBaAFCTrpmbUig3JfveqAIGJ
+6n+vAk2AMAoGCCqBHM9VAYN1A0kAMEYCIQDx+KxdaJ7YX5gR492EgiGn7//HsjOU
+B7+jyTVvkNzN2AIhAIbDKNJQ2i5Edcw/nDIWJQLec7NZui3QfC/gr9AuCfHN
+-----END CERTIFICATE-----
diff --git a/t/certs/client_enc.key b/t/certs/client_enc.key
new file mode 100644
index 000000000..154aba1d3
--- /dev/null
+++ b/t/certs/client_enc.key
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgq7+Y1ql10Uvv2vTf
+AHR26o4B3RJTJO5XTh0BNLdtB7OhRANCAARhi48/Pl7RAxIY94F9VsrTYbBiFKJ5
+IK5zXZaw3D5lASJo3c0nYyHQQAf3l5ZW60oB4zArBBmrtCCdKijeKjyE
+-----END PRIVATE KEY-----
diff --git a/t/certs/client_sign.crt b/t/certs/client_sign.crt
new file mode 100644
index 000000000..562742b10
--- /dev/null
+++ b/t/certs/client_sign.crt
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIB2TCCAX+gAwIBAgIBBDAKBggqgRzPVQGDdTBFMQswCQYDVQQGEwJBQTELMAkG
+A1UECAwCQkIxCzAJBgNVBAoMAkNDMQswCQYDVQQLDAJERDEPMA0GA1UEAwwGc3Vi
+IGNhMB4XDTIyMTEwMjAzMTkzNloXDTMyMTAzMDAzMTkzNlowSjELMAkGA1UEBhMC
+QUExCzAJBgNVBAgMAkJCMQswCQYDVQQKDAJDQzELMAkGA1UECwwCREQxFDASBgNV
+BAMMC2NsaWVudCBzaWduMFowFAYIKoEcz1UBgi0GCCqBHM9VAYItA0IABFZcc94m
+hTZRKis639AnlAbS0cKQv73GP5RzBdNlLpAaUwi4hqAh0ZUIcTH/5ZbOTal9MvHA
+gOLjVxv197o+fNejWjBYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgbAMB0GA1UdDgQW
+BBTkdzyphRCxqD6m6j/AUhMSEBASRDAfBgNVHSMEGDAWgBQk66Zm1IoNyX73qgCB
+iep/rwJNgDAKBggqgRzPVQGDdQNIADBFAiB2rcm1UI84yPYT5q6vjucBNPw01cHM
+3/Hc9fhiuYPyHwIhAIiPPPj4XI2l98C+DBaqoZtSiwDK2IA6Q8lf9SmHdFPN
+-----END CERTIFICATE-----
diff --git a/t/certs/client_sign.key b/t/certs/client_sign.key
new file mode 100644
index 000000000..22daee0bc
--- /dev/null
+++ b/t/certs/client_sign.key
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgIsAr+s4TL7K4AQWO
+PbXrJfHoO5yE2V7oYQxUBsieOQOhRANCAARWXHPeJoU2USorOt/QJ5QG0tHCkL+9
+xj+UcwXTZS6QGlMIuIagIdGVCHEx/+WWzk2pfTLxwIDi41cb9fe6PnzX
+-----END PRIVATE KEY-----
diff --git a/t/certs/gm_ca.crt b/t/certs/gm_ca.crt
new file mode 100644
index 000000000..1e7216e64
--- /dev/null
+++ b/t/certs/gm_ca.crt
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIB3zCCAYWgAwIBAgIBADAKBggqgRzPVQGDdTBGMQswCQYDVQQGEwJBQTELMAkG
+A1UECAwCQkIxCzAJBgNVBAoMAkNDMQswCQYDVQQLDAJERDEQMA4GA1UEAwwHcm9v
+dCBjYTAeFw0yMjExMDIwMzE5MzZaFw0zMjEwMzAwMzE5MzZaMEYxCzAJBgNVBAYT
+AkFBMQswCQYDVQQIDAJCQjELMAkGA1UECgwCQ0MxCzAJBgNVBAsMAkREMRAwDgYD
+VQQDDAdyb290IGNhMFowFAYIKoEcz1UBgi0GCCqBHM9VAYItA0IABB+V1+bwQsP4
+IMZEVCu3LSekz9SIhxWVVtlqdQYZG55S46PmAqICzrO3KFJ/IPtMx9wKn3L6V5M8
+hAc/UwOAnKCjYzBhMB0GA1UdDgQWBBRV7bTJ6vT1fliZR42/+E+fEBfTSjAfBgNV
+HSMEGDAWgBRV7bTJ6vT1fliZR42/+E+fEBfTSjAPBgNVHRMBAf8EBTADAQH/MA4G
+A1UdDwEB/wQEAwIBhjAKBggqgRzPVQGDdQNIADBFAiEA1SGACV1wj158Spgh+HOW
+oOr7rTO2fR4cK9Zx7eUvmAECIHbKbsw5szaC/EH7CdsHdFgrj2tWaXiQUnx/rxM/
+upAQ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIB4TCCAYegAwIBAgIBATAKBggqgRzPVQGDdTBGMQswCQYDVQQGEwJBQTELMAkG
+A1UECAwCQkIxCzAJBgNVBAoMAkNDMQswCQYDVQQLDAJERDEQMA4GA1UEAwwHcm9v
+dCBjYTAeFw0yMjExMDIwMzE5MzZaFw0zMjEwMzAwMzE5MzZaMEUxCzAJBgNVBAYT
+AkFBMQswCQYDVQQIDAJCQjELMAkGA1UECgwCQ0MxCzAJBgNVBAsMAkREMQ8wDQYD
+VQQDDAZzdWIgY2EwWjAUBggqgRzPVQGCLQYIKoEcz1UBgi0DQgAElA5ey1dYWNkT
+zfvwcKEhX1vHL+Kjil+egM6QssbNrts2S0M07L77XDe1q2zPpHjo0MR05x862/tZ
+j87OgmEE0KNmMGQwHQYDVR0OBBYEFCTrpmbUig3JfveqAIGJ6n+vAk2AMB8GA1Ud
+IwQYMBaAFFXttMnq9PV+WJlHjb/4T58QF9NKMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+DgYDVR0PAQH/BAQDAgGGMAoGCCqBHM9VAYN1A0gAMEUCIArKNHWYLmd3thQmJv89
+o0wr6O2q26WJuy6y7Eu14rdFAiEA7XNZ0JGMXPKiG5Hl7nmL8ooTrVhrmRrvs9No
+Y8rH88Y=
+-----END CERTIFICATE-----
diff --git a/t/certs/server_enc.crt b/t/certs/server_enc.crt
new file mode 100644
index 000000000..81328c57c
--- /dev/null
+++ b/t/certs/server_enc.crt
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIB2DCCAX6gAwIBAgIBAzAKBggqgRzPVQGDdTBFMQswCQYDVQQGEwJBQTELMAkG
+A1UECAwCQkIxCzAJBgNVBAoMAkNDMQswCQYDVQQLDAJERDEPMA0GA1UEAwwGc3Vi
+IGNhMB4XDTIyMTEwMjAzMTkzNloXDTMyMTAzMDAzMTkzNlowSTELMAkGA1UEBhMC
+QUExCzAJBgNVBAgMAkJCMQswCQYDVQQKDAJDQzELMAkGA1UECwwCREQxEzARBgNV
+BAMMCnNlcnZlciBlbmMwWjAUBggqgRzPVQGCLQYIKoEcz1UBgi0DQgAED+MQrLrZ
+9PbMmz/44Kb73Qc7FlMs7u034XImjJREBAn1KzZ7jqcYfCiV/buhmu1sLhMXnB69
+mERtf1tAaXcgIaNaMFgwCQYDVR0TBAIwADALBgNVHQ8EBAMCAzgwHQYDVR0OBBYE
+FBxHDo0gHhMoYkDeHWySTIJy5BZpMB8GA1UdIwQYMBaAFCTrpmbUig3JfveqAIGJ
+6n+vAk2AMAoGCCqBHM9VAYN1A0gAMEUCIHtXgpOxcb3mZv2scRZHZz5YGFr45dfk
+VfLkF9BkrB/xAiEA8EeUg7nCFfgHzrfgB7v0wgN1Hrgj8snTUO6IDfkBKYM=
+-----END CERTIFICATE-----
diff --git a/t/certs/server_enc.key b/t/certs/server_enc.key
new file mode 100644
index 000000000..0c264c199
--- /dev/null
+++ b/t/certs/server_enc.key
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgPJQ040ba9lVszMF0
+7S7pzqdxyIMc2fXKr6EsU0vRk2ahRANCAAQP4xCsutn09sybP/jgpvvdBzsWUyzu
+7TfhciaMlEQECfUrNnuOpxh8KJX9u6Ga7WwuExecHr2YRG1/W0BpdyAh
+-----END PRIVATE KEY-----
diff --git a/t/certs/server_sign.crt b/t/certs/server_sign.crt
new file mode 100644
index 000000000..3cd5e87e5
--- /dev/null
+++ b/t/certs/server_sign.crt
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIB2TCCAX+gAwIBAgIBAjAKBggqgRzPVQGDdTBFMQswCQYDVQQGEwJBQTELMAkG
+A1UECAwCQkIxCzAJBgNVBAoMAkNDMQswCQYDVQQLDAJERDEPMA0GA1UEAwwGc3Vi
+IGNhMB4XDTIyMTEwMjAzMTkzNloXDTMyMTAzMDAzMTkzNlowSjELMAkGA1UEBhMC
+QUExCzAJBgNVBAgMAkJCMQswCQYDVQQKDAJDQzELMAkGA1UECwwCREQxFDASBgNV
+BAMMC3NlcnZlciBzaWduMFowFAYIKoEcz1UBgi0GCCqBHM9VAYItA0IABKGSiyHA
+5oIeT13uNL3yxK+t9rKhAMHTzDTQA01ZylHYLn6XdksWugZsP6tTx/2+17NmRkaH
+H3wztf6ciD3WZnCjWjBYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgbAMB0GA1UdDgQW
+BBT28BTySSlomaLtnuex4LvaY8tM1TAfBgNVHSMEGDAWgBQk66Zm1IoNyX73qgCB
+iep/rwJNgDAKBggqgRzPVQGDdQNIADBFAiB4qGB+bD47KxSfSgqcedXVTd+JlL4f
+174uhGLSzNkOZwIhAI33LdJlaw+60YlZqQxzffI+gbqXpSN82+3W4vAsONN0
+-----END CERTIFICATE-----
diff --git a/t/certs/server_sign.key b/t/certs/server_sign.key
new file mode 100644
index 000000000..b2ae174c3
--- /dev/null
+++ b/t/certs/server_sign.key
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgMjFhLon1CAQdzFWt
+0Mre0juQiCDbXOY8ljWqSzQN9EehRANCAAShkoshwOaCHk9d7jS98sSvrfayoQDB
+08w00ANNWcpR2C5+l3ZLFroGbD+rU8f9vtezZkZGhx98M7X+nIg91mZw
+-----END PRIVATE KEY-----
diff --git a/t/gm/gm.t b/t/gm/gm.t
new file mode 100644
index 000000000..761c4977f
--- /dev/null
+++ b/t/gm/gm.t
@@ -0,0 +1,170 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+use t::APISIX;
+
+if (-f "/usr/local/tongsuo/bin/openssl") {
+ plan 'no_plan';
+} else {
+ plan(skip_all => "only for GM tests");
+}
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ # setup default conf.yaml
+ my $extra_yaml_config = $block->extra_yaml_config // <<_EOC_;
+plugins:
+ - gm
+_EOC_
+
+ $block->set_value("extra_yaml_config", $extra_yaml_config);
+
+ if (!$block->request) {
+ $block->set_value("request", "GET /t");
+ }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: set ssl
+--- config
+location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local t = require("lib.test_admin")
+
+ local f = assert(io.open("t/certs/server_enc.crt"))
+ local cert_enc = f:read("*a")
+ f:close()
+
+ local f = assert(io.open("t/certs/server_sign.crt"))
+ local cert_sign = f:read("*a")
+ f:close()
+
+ local f = assert(io.open("t/certs/server_enc.key"))
+ local pkey_enc = f:read("*a")
+ f:close()
+
+ local f = assert(io.open("t/certs/server_sign.key"))
+ local pkey_sign = f:read("*a")
+ f:close()
+
+ local data = {cert = cert_enc,
+ key = pkey_enc,
+ certs = {cert_sign},
+ keys = {pkey_sign},
+ sni = "localhost",
+ gm = true,
+ }
+
+ local code, body = t.test('/apisix/admin/ssls/1',
+ ngx.HTTP_PUT,
+ core.json.encode(data)
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local code, body = t.test('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/echo"
+ }]]
+ )
+
+ ngx.say(body)
+ }
+}
+--- response_body
+passed
+
+
+
+=== TEST 2: hit
+--- exec
+/usr/local/tongsuo/bin/openssl s_client -connect localhost:1994 -servername
localhost -cipher ECDHE-SM2-WITH-SM4-SM3 -enable_ntls -ntls -verifyCAfile
t/certs/gm_ca.crt -sign_cert t/certs/client_sign.crt -sign_key
t/certs/client_sign.key -enc_cert t/certs/client_enc.crt -enc_key
t/certs/client_enc.key
+--- response_body eval
+qr/^CONNECTED/
+--- no_error_log
+SSL_do_handshake() failed
+[error]
+
+
+
+=== TEST 3: reject bad SSL
+--- config
+location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local t = require("lib.test_admin")
+
+ local f = assert(io.open("t/certs/server_enc.crt"))
+ local cert_enc = f:read("*a")
+ f:close()
+
+ local f = assert(io.open("t/certs/server_enc.key"))
+ local pkey_enc = f:read("*a")
+ f:close()
+
+ local data = {
+ cert = cert_enc,
+ key = pkey_enc,
+ sni = "localhost",
+ gm = true,
+ }
+
+ local code, body = t.test('/apisix/admin/ssls/1',
+ ngx.HTTP_PUT,
+ core.json.encode(data)
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.print(body)
+ return
+ end
+ }
+}
+--- error_code: 400
+--- response_body
+{"error_msg":"sign cert\/key are required"}
+
+
+
+=== TEST 4: hit with gm disabled
+--- extra_yaml_config
+--- exec
+/usr/local/tongsuo/bin/openssl s_client -connect localhost:1994 -servername
localhost -cipher ECDHE-SM2-WITH-SM4-SM3 -enable_ntls -ntls -verifyCAfile
t/certs/gm_ca.crt -sign_cert t/certs/client_sign.crt -sign_key
t/certs/client_sign.key -enc_cert t/certs/client_enc.crt -enc_key
t/certs/client_enc.key
+--- response_body
+--- error_log
+SSL_do_handshake() failed