0


Netty集成国密开源基础密码库Tongsuo

前言

    当前的软件形势来看,国密和信创都是大势所趋。项目组中也越来越多场景要求使用通道加密。维护的项目中大多用到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系统一般服务器环境没有应用场景,有能力的小伙伴可以帮忙测试一下。

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

标签: ssl java

本文转载自: https://blog.csdn.net/x1075339587/article/details/130513163
版权归原作者 Boeing_Bick 所有, 如有侵权,请联系我们删除。

“Netty集成国密开源基础密码库Tongsuo”的评论:

还没有评论