前言
当前的软件形势来看,国密和信创都是大势所趋。项目组中也越来越多场景要求使用通道加密。维护的项目中大多用到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架构的编译。
待完成的工作:
- TLSv1.3的TLS_SM4_GCM_SM3套件的支持。
- ARM架构、PPC架构、Windows的编译。mac系统一般服务器环境没有应用场景,有能力的小伙伴可以帮忙测试一下。
时代的车轮在加速的运转,在有限的视野里也要不断地追赶。
版权归原作者 Boeing_Bick 所有, 如有侵权,请联系我们删除。