bc-java icon indicating copy to clipboard operation
bc-java copied to clipboard

Add GMSSL support

Open Trisia opened this issue 3 years ago • 14 comments

GMSSL follow the specification《GMT 0024-2014 SSL VPN技术规范》

GMSSL a variant of TLS1.1(RFC4346)

Use independent protocol version number 0x0101 Cipher suite using GM algorithm SM2_SM4_SM3

  • SM2 for auth
  • SM4 for encrypt
  • SM3 for hmac

certficate message contain two cert, first for sign second for encypt.

more difference see https://blog.csdn.net/q1009020096/article/details/114321986?spm=1001.2014.3001.5501

For testing and use, see:

  • tls/src/test/java/org/bouncycastle/tls/test/GMSimpleSSLSocketFactoryTest.java
  • tls/src/test/java/org/bouncycastle/tls/test/GMSSLClientTest.java
  • tls/src/test/java/org/bouncycastle/tls/test/GMSSLServerTest.java

more:

reference:

[1]. IETF. RFC4346 . 2006 [2]. 密码行业标准化技术委员会 . GMT 0024-2014 SSL VPN技术规范 . 2014

Trisia avatar Mar 16 '21 10:03 Trisia

Apache HTTPClient GMSSL Example:

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.bouncycastle.jsse.provider.gm.GMSimpleSSLSocketFactory;

import java.security.Security;

/**
 * GMSSL Http Client test
 *
 * @author Cliven
 * @since 2021-02-05 13:25:06
 */
public class GMHttpClient {

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        GMSimpleSSLSocketFactory factory = new GMSimpleSSLSocketFactory();

        SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(factory,  new NoopHostnameVerifier());
        HttpClient client = HttpClientBuilder.create()
                .setSSLSocketFactory(sf)
                .build();

        final HttpResponse response = client.execute(new HttpGet("https://127.0.0.1:5557"));
        response.getEntity().writeTo(System.out);
    }
}

Trisia avatar Mar 17 '21 08:03 Trisia

Just one thing, we do not allow author tags (which I'll admit can be annoying as most IDEs now insist on inserting one when you create a file, here we take the attitude that if anything's broken, it's all our problem). I think we can accept this otherwise. I just wanted to check that was understood.

dghgit avatar Mar 21 '21 01:03 dghgit

Just one thing, we do not allow author tags (which I'll admit can be annoying as most IDEs now insist on inserting one when you create a file, here we take the attitude that if anything's broken, it's all our problem). I think we can accept this otherwise. I just wanted to check that was understood.

I have removed all @author tags. Is there anything else I need to adjust?

Trisia avatar Mar 21 '21 04:03 Trisia

Thanks. Just one other thing, is this meant to be compliant with RFC 8998? I just noticed there was a comment concerning TLS 1.1. at the top.

Okay, I've done a bit more reading. This is different from RFC 8998 isn't it? In some ways it's not really TLS is it, it's more a TLS like protocol. Would that be correct?

dghgit avatar Mar 21 '21 05:03 dghgit

Thanks. Just one other thing, is this meant to be compliant with RFC 8998? I just noticed there was a comment concerning TLS 1.1. at the top.

Okay, I've done a bit more reading. This is different from RFC 8998 isn't it? In some ways it's not really TLS is it, it's more a TLS like protocol. Would that be correct?

Correct.

It is more like a dialect based on a standard language.

Although it is called SSL, it is closer to TLS because it is modified from TLS RFC4346. Most of the content is the same as TLS1.1, except that ShangMi (SM) Cipher is used.

I personally think that it may be more appropriate to call it GMTLS, but it is called GMSSL in GMT0024-2012, which is just a difference in name.

The latest version of Chinese cipher algorithm has been added in TLS1.3, I remember the protocol number is RFC8998 ShangMi (SM) Cipher Suites for TLS 1.3, they are not the same thing.

RFC8998 describes the addition of ShangMi (SM) algorithm suite to TLS1.3, while and GMT0024-2012 is an independent revision branch based on TLS1.1.

Trisia avatar Mar 21 '21 10:03 Trisia

One question that has come up is how does the Certificate message work in the SM2 key exchange algorithm. There appear to be 2 end-entity certificates in the message, are there going to be others in the chain and do the two certificate have a common issuer?

dghgit avatar Mar 22 '21 07:03 dghgit

One question that has come up is how does the Certificate message work in the SM2 key exchange algorithm. There appear to be 2 end-entity certificates in the message, are there going to be others in the chain and do the two certificate have a common issuer?

Yes, the two certificate have same issuer, and the server certificate message contains only two certificates without other certificate chains, one for signing and the other is used for encryption

GMT0024-2012 6.4.4.2 Server Certificate Message struct as follows: image

Server certificate: The signing certificate comes first, and the encryption certificate comes behind.

If you need to a SM2 server certificate, you need to submit a certificate request to CA, then you will receive:

  • two certificates(one cert for signing, other for encryption )
  • one SignedAndEnvelopedData (encrypted a SM2 key pair inside), you can use priviate key of sign cert to decrypt this.

It is different from RSA in TLS1.1. In TLS1.1, both signature and encryption use the same pair of secret keys, while GMSSL uses two keypairs to work independently.

The handshake message flow is basically the same as TLS1.1:

image

I drew a picture about the key exchange of SM2_SM4_SM3 cipher suite below:

GM密钥交换流程

The picture only describes the key exchange related messages, and does not completely cover the entire handshake process.

Trisia avatar Mar 22 '21 09:03 Trisia

We've finished analysing the patches. It's been quite a valuable exercise in the sense that it has shown up some shortcomings in our approach. We feel that treating GM SSL as a branch of TLS 1.1 may be the best way to go also. You'll notice more and more conflicts with your patch as we do the merge. We'll let you know when we are done, hopefully at that point we'll something you can simply start using. There is no need to make any changes at the moment.

dghgit avatar Mar 24 '21 08:03 dghgit

We've finished analysing the patches. It's been quite a valuable exercise in the sense that it has shown up some shortcomings in our approach. We feel that treating GM SSL as a branch of TLS 1.1 may be the best way to go also. You'll notice more and more conflicts with your patch as we do the merge. We'll let you know when we are done, hopefully at that point we'll something you can simply start using. There is no need to make any changes at the moment.

ok, thanks

Trisia avatar Mar 24 '21 09:03 Trisia

We've finished analysing the patches. It's been quite a valuable exercise in the sense that it has shown up some shortcomings in our approach. We feel that treating GM SSL as a branch of TLS 1.1 may be the best way to go also. You'll notice more and more conflicts with your patch as we do the merge. We'll let you know when we are done, hopefully at that point we'll something you can simply start using. There is no need to make any changes at the moment.

When will this branch be merged

Trisia avatar Apr 27 '21 01:04 Trisia

We're still working through it. You should see bits of it showing up now though.

dghgit avatar May 12 '21 05:05 dghgit

Apache HTTPClient GMSSL Example:

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.bouncycastle.jsse.provider.gm.GMSimpleSSLSocketFactory;

import java.security.Security;

/**
 * GMSSL Http Client test
 *
 * @author Cliven
 * @since 2021-02-05 13:25:06
 */
public class GMHttpClient {

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        GMSimpleSSLSocketFactory factory = new GMSimpleSSLSocketFactory();

        SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(factory,  new NoopHostnameVerifier());
        HttpClient client = HttpClientBuilder.create()
                .setSSLSocketFactory(sf)
                .build();

        final HttpResponse response = client.execute(new HttpGet("https://127.0.0.1:5557"));
        response.getEntity().writeTo(System.out);
    }
}

请问下 示例中客户端的证书改如何设置?客户端连接的时候不是传递ca证书、签名证书|密钥、加密证书|密钥即可,但是 GMSSKeyParameters 构造函数很不明确

sixinyiyu avatar May 21 '21 03:05 sixinyiyu

Apache HTTPClient GMSSL Example:

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.bouncycastle.jsse.provider.gm.GMSimpleSSLSocketFactory;

import java.security.Security;

/**
 * GMSSL Http Client test
 *
 * @author Cliven
 * @since 2021-02-05 13:25:06
 */
public class GMHttpClient {

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        GMSimpleSSLSocketFactory factory = new GMSimpleSSLSocketFactory();

        SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(factory,  new NoopHostnameVerifier());
        HttpClient client = HttpClientBuilder.create()
                .setSSLSocketFactory(sf)
                .build();

        final HttpResponse response = client.execute(new HttpGet("https://127.0.0.1:5557"));
        response.getEntity().writeTo(System.out);
    }
}

请问下 示例中客户端的证书改如何设置?客户端连接的时候不是传递ca证书、签名证书|密钥、加密证书|密钥即可,但是 GMSSKeyParameters 构造函数很不明确

目前我还没有时间去处理客户端对服务端证书的验证,以及双向身份认证,目前的这个分支仅仅实现了单向的gmssl

Trisia avatar May 22 '21 02:05 Trisia

Apache HTTPClient GMSSL Example:

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.bouncycastle.jsse.provider.gm.GMSimpleSSLSocketFactory;

import java.security.Security;

/**
 * GMSSL Http Client test
 *
 * @author Cliven
 * @since 2021-02-05 13:25:06
 */
public class GMHttpClient {

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        GMSimpleSSLSocketFactory factory = new GMSimpleSSLSocketFactory();

        SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(factory,  new NoopHostnameVerifier());
        HttpClient client = HttpClientBuilder.create()
                .setSSLSocketFactory(sf)
                .build();

        final HttpResponse response = client.execute(new HttpGet("https://127.0.0.1:5557"));
        response.getEntity().writeTo(System.out);
    }
}

请问下 示例中客户端的证书改如何设置?客户端连接的时候不是传递ca证书、签名证书|密钥、加密证书|密钥即可,但是 GMSSKeyParameters 构造函数很不明确

目前我还没有时间去处理客户端对服务端证书的验证,以及双向身份认证,目前的这个分支仅仅实现了单向的gmssl

访问 https://demo.gmssl.cn 没有问题,但有一些使用国密证书的URL会抛出这样的握手失败的异常,造成连接失败: 出现问题的URL使用了SM4_CBC(加密)+HMAC_SM3(数字摘要)+SM2(密钥交换) 密码套件。好奇怪

Exception in thread "main" org.bouncycastle.tls.TlsFatalAlert: handshake_failure(40)
        at org.bouncycastle.tls.AbstractTlsPeer.notifySecureRenegotiation(AbstractTlsPeer.java:121)
        at org.bouncycastle.tls.TlsClientProtocol.processServerHello(TlsClientProtocol.java:1216)
        at org.bouncycastle.tls.TlsClientProtocol.handleHandshakeMessage(TlsClientProtocol.java:497)
        at org.bouncycastle.tls.TlsProtocol.processHandshakeQueue(TlsProtocol.java:629)
        at org.bouncycastle.tls.TlsProtocol.processRecord(TlsProtocol.java:519)
        at org.bouncycastle.tls.RecordStream.readRecord(RecordStream.java:245)
        at org.bouncycastle.tls.TlsProtocol.safeReadRecord(TlsProtocol.java:777)

        at org.bouncycastle.tls.TlsProtocol.blockForHandshake(TlsProtocol.java:370)
        at org.bouncycastle.tls.TlsClientProtocol.connect(TlsClientProtocol.java:86)
        at org.bouncycastle.jsse.provider.gm.GMSimpleSSLSocket.makeHandshake(GMSimpleSSLSocket.java:181)
        at org.bouncycastle.jsse.provider.gm.GMSimpleSSLSocketWrap.startHandshake(GMSimpleSSLSocketWrap.java:271)
        at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:396)
        at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:355)
        at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
        at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:373)
        at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:394)
        at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:237)
        at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
        at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
        at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
        at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)

UPDATE:找到原因了,在TlsClientProtocol类里发送了重新协商请求,如果server端不支持重新协商那么就会Alert40。目前暂时注释掉这行即可。

 // TODO[compat-gnutls] GnuTLS test server fails to send renegotiation_info extension when resuming
tlsClient.notifySecureRenegotiation(securityParameters.isSecureRenegotiation());

cleverpig avatar Sep 16 '21 05:09 cleverpig

Hi @dghgit , Do you have plan to merge this pr or there is another solution to support GMSSL from official?

jsuper avatar May 19 '23 03:05 jsuper