目录

一、Tongsuo是啥?

二、为什么选择Tongsuo?

三、Netty的实现

四、编译方法

 1.Netty

 2.Netty-tcnative

 3.Tongsuo

五、集成过程

 1.Netty-tcnative编译Tongsuo密码库

 2.Netty-tcnative调用Tongsuo密码库

 3.Netty调用Netty-tcnative

六、测试TLCP协议

总结


前言

        当前的软件形势来看,国密和信创都是大势所趋。项目组中也越来越多场景要求使用通道加密。维护的项目中大多用到netty框架,netty默认支持SSL/TLS,但都是国际算法,当前openssl对国密算法支持仅限于基础算法层面,还没有涉及到通道加密。记录一下Netty适配国密SSL通道加密的过程,避免遗忘,也算是让自己这段时间的成果不白费。


一、Tongsuo是啥?
铜锁开源密码库 · 语雀铜锁/Tongsuo(原BabaSSL)是一个提供现代...https://www.yuque.com/tsdoc

二、为什么选择Tongsuo?

        简单粗暴,选择Tongsuo是因为他获得了国密资质,了解这个东西的人估计都会选择Tongsuo吧。

三、Netty的实现

        Netty调用Netty-tcnative组件,Netty-tcnative调用集成Openssl算法库的JNI动态库。集成思路:将Openssl替换为Tongsuo。

        Netty编译需要依赖Netty-tcnative组件。Netty-tcnative使用Maven编译时会去下载密码库代码,编译密码库组件,并将密码库的库文件静态依赖到JNI动态库中。由此完成Netty到密码库的通道加密的调用。

四、编译方法

 1.Netty

mvnw clean install \
 -DskipTests \
 -Dmaven.test.skip=true \
 -Dcheckstyle.skip=true 

 2.Netty-tcnative

mvnw clean install

 3.Tongsuo

config -O3 -fno-omit-frame-pointer -fPIC \
no-ssl3 no-shared no-comp \
-DOPENSSL_NO_HEARTBEATS \
--strict-warnings enable-ntls \
--prefix=${opensslHome} --openssldir=${opensslHome}

make -j

make install

五、集成过程

 1.Netty-tcnative编译Tongsuo密码库

        Netty-tcnative主要分为openssl-classes(JNI Java类模块)、openssl-dynamic(JNI 动态库C代码模块)、openssl-static(动态库集成密码库模块),其他的两个模块boringssl-static和libressl-static没用过,没有深入了解。maven编译时直接注释了。

openssl-static pom.xml中编译openssl有windows、linux、mac不同系统的profile,对应替换Tongsuo下载路径:

<configuration>
  <target>
	<taskdef resource="net/sf/antcontrib/antcontrib.properties" />
	<if>
	  <available file="${opensslSourceDir}" />
	  <then>
		<echo message="OpenSSL was already downloaded, skipping the build step." />
	  </then>
	  <else>
		<echo message="Downloading Tongsuo" />
		<mkdir dir="${opensslSourceDir}" />
		<get 
src="https://codeload.github.com/Tongsuo-Project/Tongsuo/zip/refs/heads/master" 
dest="${project.build.directory}/tongsuo-${opensslVersion}.zip" verbose="on" />
		<exec executable="unzip" failonerror="true" 
dir="${project.build.directory}/" resolveexecutable="true">
		   <arg line="tongsuo-${opensslVersion}.zip" />
		 </exec>
		<move file="${project.build.directory}/Tongsuo-master"
 tofile="${opensslSourceDir}" />
	  </else>
	</if>
  </target>
</configuration>

替换Tongsuo编译方式,只进行了Linux的测试:

<configuration>
  <target>
	<taskdef resource="net/sf/antcontrib/antcontrib.properties" />

	<if>
	  <available file="${opensslHome}" />
	  <then>
		<echo message="OpenSSL was already build, skipping the build step." />
	  </then>
	  <else>
		<echo message="Building OpenSSL" />
		<mkdir dir="${opensslHome}" />
		<exec executable="config" failonerror="true" dir="${opensslSourceDir}" 
resolveexecutable="true">
		  <arg line="-O3 -fno-omit-frame-pointer -fPIC 
no-ssl3 no-shared no-comp -DOPENSSL_NO_HEARTBEATS
		  --strict-warnings enable-ntls
		  --prefix=${opensslHome} --openssldir=${opensslHome}" />
		</exec>
		<exec executable="make" failonerror="true" dir="${opensslSourceDir}" 
resolveexecutable="true">
		  <arg value="depend" />
		</exec>
		<exec executable="make" failonerror="true" dir="${opensslSourceDir}" 
resolveexecutable="true" />
		<exec executable="make" failonerror="true" dir="${opensslSourceDir}" 
resolveexecutable="true">
		  <!-- Don't install manpages to make things as fast a possible -->
		  <arg value="install_sw" />
		</exec>
	  </else>
	</if>
  </target>
</configuration>

  2.Netty-tcnative调用Tongsuo密码库

        调用方式参考Tongsuo文档《NTLS 使用手册》:

NTLS 使用手册 · 语雀编译 NTLS 功能NTLS 在 Tongsuo 的术...https://www.yuque.com/tsdoc/ts/hedgqf        修改openssl-dynamic中sslcontent.c文件中的make方法中更换为NTLS的握手方式;扩展setCertificate方法和setCertificateBio方法增加对国密双证的支持。

TCN_IMPLEMENT_CALL(jlong, SSLContext, make)(TCN_STDARGS, jint protocol, jint mode)
{
...
    if (mode == SSL_MODE_CLIENT) {
        ctx = SSL_CTX_new(NTLS_client_method());
        SSL_CTX_enable_ntls(ctx);
    } else if (mode == SSL_MODE_SERVER) {
        ctx = SSL_CTX_new(NTLS_server_method());
        SSL_CTX_enable_ntls(ctx);
    } else {
        ctx = SSL_CTX_new(TLS_method());
    }
...
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificate)
(TCN_STDARGS, jlong ctx,jstring enccert, jstring enckey,
jstring signcert, jstring signkey, jstring password)
{
...
    enc_key_file  = J2S(enckey);
    enc_cert_file = J2S(enccert);
    sign_key_file  = J2S(signkey);
    sign_cert_file = J2S(signcert);
    if (!enc_key_file) {
        enc_key_file = enc_cert_file;
    }
    if (!sign_key_file) {
        sign_key_file = sign_cert_file;
    }
    if (!enc_key_file || !enc_cert_file) {
        tcn_Throw(e, "No Enc Certificate file specified or invalid file format");
        rv = JNI_FALSE;
        goto cleanup;
    }
    if (!sign_key_file || !sign_cert_file) {
        tcn_Throw(e, "No Sign Certificate file specified or invalid file format");
        rv = JNI_FALSE;
        goto cleanup;
    }
    if ((p = strrchr(enc_cert_file, '.')) != NULL && strcmp(p, ".pkcs12") == 0) {
        if (!ssl_load_pkcs12(c, enc_cert_file, &encpkey, &encxcert, 0)) {
            ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
            tcn_Throw(e, "Unable to load certificate %s (%s)",
                      enc_cert_file, err);
            rv = JNI_FALSE;
            goto cleanup;
        }
    } else {
        if ((encpkey = load_pem_key(c, enc_key_file)) == NULL) {
            ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
            tcn_Throw(e, "Unable to load certificate key %s (%s)",
                      enc_key_file, err);
            rv = JNI_FALSE;
            goto cleanup;
        }
        if ((encxcert = load_pem_cert(c, enc_cert_file)) == NULL) {
            ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
            tcn_Throw(e, "Unable to load certificate %s (%s)",
                      enc_cert_file, err);
            rv = JNI_FALSE;
            goto cleanup;
        }
    }
    if ((p = strrchr(sign_cert_file, '.')) != NULL && strcmp(p, ".pkcs12") == 0) {
        if (!ssl_load_pkcs12(c, sign_cert_file, &signpkey, &signxcert, 0)) {
            ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
            tcn_Throw(e, "Unable to load certificate %s (%s)",
                      sign_cert_file, err);
            rv = JNI_FALSE;
            goto cleanup;
        }
    } else {
        if ((signpkey = load_pem_key(c, sign_key_file)) == NULL) {
            ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
            tcn_Throw(e, "Unable to load certificate key %s (%s)",
                      sign_key_file, err);
            rv = JNI_FALSE;
            goto cleanup;
        }
        if ((signxcert = load_pem_cert(c, sign_cert_file)) == NULL) {
            ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
            tcn_Throw(e, "Unable to load certificate %s (%s)",
                      sign_cert_file, err);
            rv = JNI_FALSE;
            goto cleanup;
        }
    }
    if (SSL_CTX_use_enc_certificate(c->ctx, encxcert) <= 0) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        tcn_Throw(e, "Error setting enc certificate (%s)", err);
        rv = JNI_FALSE;
        goto cleanup;
    }
    if (SSL_CTX_use_sign_certificate(c->ctx, signxcert) <= 0) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        tcn_Throw(e, "Error setting sign certificate (%s)", err);
        rv = JNI_FALSE;
        goto cleanup;
    }
    if (SSL_CTX_use_enc_PrivateKey(c->ctx, encpkey) <= 0) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        tcn_Throw(e, "Error setting enc private key (%s)", err);
        rv = JNI_FALSE;
        goto cleanup;
    }
    if (SSL_CTX_use_sign_PrivateKey(c->ctx, signpkey) <= 0) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        tcn_Throw(e, "Error setting sign private key (%s)", err);
        rv = JNI_FALSE;
        goto cleanup;
    }
...
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateBio)
(TCN_STDARGS, jlong ctx,jlong enccert, jlong enckey,
jlong signcert, jlong signkey,jstring password)
{
...
    if (!enckey) {
        enckey = enccert;
    }
    if (!enccert || !enckey) {
        tcn_Throw(e, "No Enc Certificate file specified or invalid file format");
        rv = JNI_FALSE;
        goto cleanup;
    }
    if (!signkey) {
        signkey = signcert;
    }
    if (!signcert || !signkey) {
        tcn_Throw(e, "No Sign Certificate file specified or invalid file format");
        rv = JNI_FALSE;
        goto cleanup;
    }
    if ((enc_pkey = tcn_load_pem_key_bio(c->password, enc_key_bio)) == NULL) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        ERR_clear_error();
        tcn_Throw(e, "Unable to load Enc certificate key (%s)",err);
        rv = JNI_FALSE;
        goto cleanup;
    }
    if ((enc_xcert = tcn_load_pem_cert_bio(c->password, enc_cert_bio)) == NULL) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        ERR_clear_error();
        tcn_Throw(e, "Unable to load Enc certificate (%s) ", err);
        rv = JNI_FALSE;
        goto cleanup;
    }
    if ((sign_pkey = tcn_load_pem_key_bio(c->password, sign_key_bio)) == NULL) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        ERR_clear_error();
        tcn_Throw(e, "Unable to load Sign certificate key (%s)",err);
        rv = JNI_FALSE;
        goto cleanup;
    }
    if ((sign_xcert = tcn_load_pem_cert_bio(c->password, sign_cert_bio)) == NULL) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        ERR_clear_error();
        tcn_Throw(e, "Unable to load Sign certificate (%s) ", err);
        rv = JNI_FALSE;
        goto cleanup;
    }
    if (SSL_CTX_use_enc_certificate(c->ctx, enc_xcert) <= 0) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        ERR_clear_error();
        tcn_Throw(e, "Error setting enc certificate (%s)", err);
        rv = JNI_FALSE;
        goto cleanup;
    }
    if (SSL_CTX_use_sign_certificate(c->ctx, sign_xcert) <= 0) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        ERR_clear_error();
        tcn_Throw(e, "Error setting sign certificate (%s)", err);
        rv = JNI_FALSE;
        goto cleanup;
    }
    if (SSL_CTX_use_enc_PrivateKey(c->ctx, enc_pkey) <= 0) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        ERR_clear_error();
        tcn_Throw(e, "Error setting enc private key (%s)", err);
        rv = JNI_FALSE;
        goto cleanup;
    }
    if (SSL_CTX_use_sign_PrivateKey(c->ctx, sign_pkey) <= 0) {
        ERR_error_string_n(ERR_get_error(), err, ERR_LEN);
        ERR_clear_error();
        tcn_Throw(e, "Error setting sign private key (%s)", err);
        rv = JNI_FALSE;
        goto cleanup;
    }
...
}

  3.Netty调用Netty-tcnative

        Netty的改动主要是对应国密双证的支持。握手通过SSLContext.make方法完成。改动内容在handler模块ssl包中。

CipherSuiteConverter.java 添加国密套件映射。Java中使用下滑线的套件名,需要映射到Openssl的连字符的套件名。反正是个名字,我给他重新定义了一下:

 TLCP_ECDHE_SM4_CBC_SM3 >> ECDHE-SM2-SM4-CBC-SM3
 TLCP_ECDHE_SM4_GCM_SM3 >> ECDHE-SM2-SM4-GCM-SM3
 TLCP_ECC_SM4_CBC_SM3 >> ECC-SM2-SM4-CBC-SM3
 TLCP_ECC_SM4_GCM_SM3 >> ECC-SM2-SM4-GCM-SM3

SslContextBuilder.java 增加国密双证的支持。

SslUtils.java        添加国密套件。

 ReferenceCountedOpenSslClientContext.java、ReferenceCountedOpenSslServerContext.java客户端和服务端握手类,添加国密双证的支持。

六、测试TLCP协议

final SslContext sslCtx = SslContextGMBuilder.forClient().protocols()
		.trustManager(SysConfigMangrClient.TRUST_CERT)
		.keyManager(SysConfigMangrClient.ENC_CERT, SysConfigMangrClient.ENC_KEY,
				SysConfigMangrClient.SIGN_CERT, SysConfigMangrClient.SIGN_KEY,
				SysConfigMangrClient.KEY_PASSWORD == null ? null
						: new String(SysConfigMangrClient.KEY_PASSWORD),
				new String[]{SysConfigMangrClient.ROOT_CERT})
        // 客户端控制国密套件
		.ciphers(Arrays.asList("TLCP_ECDHE_SM4_CBC_SM3"))
		.build();

代码参见:

fury_fox · master · t-camp · AtomGit


总结

        本次完成了Tongsuo TLCP协议的四种国密套件;X86架构的编译。

待完成的工作:

  1. TLSv1.3的TLS_SM4_GCM_SM3套件的支持。
  2. ARM架构、PPC架构、Windows的编译。mac系统一般服务器环境没有应用场景,有能力的小伙伴可以帮忙测试一下。

时代的车轮在加速的运转,在有限的视野里也要不断地追赶。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐