Coder Social home page Coder Social logo

gm-jsse's Introduction

English | 简体中文

GM JSSE

Latest Stable Version Java CI with Maven

Requirements

  • JDK 1.7 or later.

Installation

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>gmsse</artifactId>
    <version>{{see the version on the badge}}</version>
</dependency>

Usage

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.net.URL;

import com.aliyun.gmsse.GMProvider;

public class Main {

    public static void main(String[] args) throws Exception {
        // init SSLSocketFactory
        GMProvider provider = new GMProvider();
        SSLContext sc = SSLContext.getInstance("TLS", provider);
        sc.init(null, null, null);
        SSLSocketFactory ssf = sc.getSocketFactory();

        URL serverUrl = new URL("https://xxx/");
        HttpsURLConnection conn = (HttpsURLConnection) serverUrl.openConnection();
        conn.setRequestMethod("GET");
        // set SSLSocketFactory
        conn.setSSLSocketFactory(ssf);
        conn.connect();
        System.out.println("used cipher suite:");
        System.out.println(conn.getCipherSuite());
    }
}

In the new version, GM-JSSE will verify server and CA certificates, if the CA root certificates are not imported in system, maybe have verfication errors. So you need add trust manager with CA certificates.

    BouncyCastleProvider bc = new BouncyCastleProvider();
    KeyStore ks = KeyStore.getInstance("JKS");
    CertificateFactory cf = CertificateFactory.getInstance("X.509", bc);
    FileInputStream is = new FileInputStream("/path/to/ca_cert");
    X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
    ks.load(null, null);
    ks.setCertificateEntry("gmca", cert);

    TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509", provider);
    tmf.init(ks);

    sc.init(null, tmf.getTrustManagers(), null);
    SSLSocketFactory ssf = sc.getSocketFactory();

Two-way Authentication

In two-way authentication, the client needs to pass in two certificates.

    public static X509Certificate loadCertificate(String path) throws KeyStoreException, CertificateException, FileNotFoundException {
        BouncyCastleProvider bc = new BouncyCastleProvider();
        CertificateFactory cf = CertificateFactory.getInstance("X.509", bc);
        InputStream is = Server.class.getClassLoader().getResourceAsStream(path);
        X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
        return cert;
    }

    public static PrivateKey loadPrivateKey(String path) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        InputStream is = Server.class.getClassLoader().getResourceAsStream(path);
        InputStreamReader inputStreamReader = new InputStreamReader(is);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        StringBuilder sb = new StringBuilder();
        String line = null;
            while ((line = bufferedReader.readLine()) != null){
            if (line.startsWith("-")){
                continue;
            }
            sb.append(line).append("\n");
        }
        String ecKey = sb.toString().replaceAll("\\r\\n|\\r|\\n", "");
        Base64.Decoder base64Decoder = Base64.getDecoder();
        byte[] keyByte = base64Decoder.decode(ecKey.getBytes(StandardCharsets.UTF_8));
        PKCS8EncodedKeySpec eks2 = new PKCS8EncodedKeySpec(keyByte);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
        PrivateKey privateKey = keyFactory.generatePrivate(eks2);
        return privateKey;
    }

    KeyStore ks = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());
    ks.load(null, null);

    // 传入签名证书
    ks.setKeyEntry("sign", loadPrivateKey("sm2/client_sign.key"), new char[0], new X509Certificate[] {
        loadCertificate("sm2/client_sign.crt")
    });
    // 传入加密证书
    ks.setKeyEntry("enc", Server.loadPrivateKey("sm2/client_enc.key"), new char[0], new X509Certificate[] {
        oadCertificate("sm2/client_enc.crt")
    });

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(ks, new char[0]);

    // 传入根证书
    ks.setCertificateEntry("gmca", loadCertificate("sm2/chain-ca.crt"));

    TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509", provider);
    tmf.init(ks);

    sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    SSLSocketFactory ssf = sc.getSocketFactory();

Issues

Opening an Issue, Issues not conforming to the guidelines may be closed immediately.

Changelog

Detailed changes for each release are documented in the release notes.

License

Apache-2.0

Copyright (c) 2009-present, Alibaba Cloud All rights reserved.

gm-jsse's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gm-jsse's Issues

gm-jsse与gmssl工具不兼容

有个开源国密工具gmssl,https://github.com/guanzhi/GmSSL/tree/GmSSL-v2。(注意是v2分支)。
这个工具与openssl兼容,可以生成国密自签名证书,也可以作为国密服务端或客户端。

我用gm-jsse作了服务端,启动了com.aliyun.gmsse.server.ClientTest的testServer
(代码有一处改动:com.aliyun.gmsse.server.Server里面改了ss.setNeedClientAuth(false);)

我用这个工具作为客户端连接gm-jsse服务端,
gmssl s_client -connect 127.0.0.1:8443 -gmtls
发现报以下错误。

javax.net.ssl.SSLException: decrypt pre master secret failed at com.aliyun.gmsse.protocol.ServerConnectionContext.receiveClientKeyExchange(ServerConnectionContext.java:192) at com.aliyun.gmsse.protocol.ServerConnectionContext.kickstart(ServerConnectionContext.java:96) at com.aliyun.gmsse.GMSSLSocket.startHandshake(GMSSLSocket.java:215) at com.aliyun.gmsse.GMSSLSocket$AppDataInputStream.read(GMSSLSocket.java:310) at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) at java.io.InputStreamReader.read(InputStreamReader.java:184) at java.io.BufferedReader.fill(BufferedReader.java:161) at java.io.BufferedReader.readLine(BufferedReader.java:324) at java.io.BufferedReader.readLine(BufferedReader.java:389) at com.aliyun.gmsse.server.ClientTest$1.run(ClientTest.java:48) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.IllegalArgumentException: Invalid point coordinates at org.bouncycastle.math.ec.ECCurve.validatePoint(Unknown Source) at org.bouncycastle.math.ec.ECCurve.decodePoint(Unknown Source) at org.bouncycastle.crypto.engines.SM2Engine.decrypt(Unknown Source) at org.bouncycastle.crypto.engines.SM2Engine.processBlock(Unknown Source) at com.aliyun.gmsse.crypto.Crypto.decrypt(Crypto.java:100) at com.aliyun.gmsse.protocol.ServerConnectionContext.receiveClientKeyExchange(ServerConnectionContext.java:190) ... 12 more
可以解决一下这个问题吗?

为什么有些国密网站访问不通

下面这4个网站都是国密SSL网站,用密信浏览器都能够访问且被标注为国密网站,但是用gm-jsse却访问不了有些网站,如下:
能访问通:https://ebssec.boc.cn、https://sm2only.ovssl.cn
访问不通:https://www.hunan.gov.cn、https://www.creditjx.gov.cn/?trust=true
因为我对国密SSL的握手过程不太了解,所以来这提问,请帮忙回答下,感激不尽。
粗略跟了下代码,发现是签名验证不通过,但是具体的算法我看不懂。

https下载文件过大会阻塞

访问国密Nginx下载文件只能下载几KB的文件,文件稍微大一些就无法完整下载,会阻塞住,通过抓包看,服务器发到一定大小的包后就不再发包了,用国密的浏览器访问就可以完整下载。

关于ECDHE_SM4_CBC_SM3的实现

0、ECDHE交换算法测试过的主机:https://e.tf.cn(须指定ECDHE_SM4_CBC_SM3否则服务器协商优先ECC_SM4_CBC_SM3)和https://demo.gmssl.cn:1443
1、在ServerKeyExchange阶段,按下面描述保存服务器公钥(ephemeral):
// 03 -- ECCurveType-->named_curve(3) -- 目前只有这个合法值,其他要么废弃要么是RFU的了
// 00 17 -- secp256r1 (23) sm2p256v1(41) -- 按TLCP规范,这里是不用校验的
// 41 -- length
// 04 -- SM2 public key(point = x + y)
// 8fc16899e15b5110b1b3cf45a331bd8b9c25bf2afb0f0b31faa1e7106a7f36bae75bba7938ddacbe5868fe4e64755db631199c95802c550e4342dbcbd54b937a
// 00 47 -- 下面为ASN.1签名数据signed_param(用服务器签名公钥验签,验签数据:client random+server random + ECDHE parameter)
// 30 45
// 02 20 3ed134bc05dbc842a31c83bf6b5c89ba2c504257736ab436d6e65ecbcac53dc8 -R
// 02 21 00b2541eb483f7faaa5f22e0502049104944d8a423de69a2eac757d1c6cfca5e3f -S
2、在ClientKeyExchange阶段,计算PreMasterKey,借助于 BC库的 SM2KeyExchange类;然后把Ephemeral 的PublicKey发给服务器
PrivateKey localEncPVK = session.getKeyManager().getPrivateKey(GMConstants.CERT_ENC);
KeyPair localEphKP = Crypto.generateSM2KeyPair(); // Ephemeral key pair
PublicKey peerEphPUK = Crypto.loadPublicKey(session.getServerKEParam()); // 这里就是之前SKE的时候服务器的ECPoint为PublicKey
byte[] plainSecret = Crypto.generateECDHESecret(localEncPVK, localEphKP.getPrivate(), peerEncPUK, peerEphPUK);

核心代码如下:

public static KeyPair generateSM2KeyPair()
        throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
    KeyPairGenerator generator = KeyPairGenerator.getInstance(“EC”, GMProvider.BC_PROVIDER); // new BouncyCastleProvider()
    generator.initialize(new ECGenParameterSpec(GM_NAME));  // GM_NAME="sm2p256v1"

    return generator.generateKeyPair();
}

public static PublicKey loadPublicKey(byte[] pointBytes)
        throws InvalidKeySpecException, NoSuchAlgorithmException {
    ECParameterSpec ecParamSpec = org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec(GM_NAME);
    ECPoint ecPoint = ecParamSpec.getCurve().decodePoint(pointBytes);
    ECPublicKeySpec ecPUKSpec = new ECPublicKeySpec(ecPoint, ecParamSpec);

    KeyFactory keyFactory = KeyFactory.getInstance("EC", GMProvider.BC_PROVIDER);
    return keyFactory.generatePublic(ecPUKSpec);
}

public static byte[] generateECDHESecret(PrivateKey localEncPVK, PrivateKey localEphPVK, PublicKey peerEncPUK, PublicKey peerEphPUK) {
    CipherParameters sm2kePvkParam = new SM2KeyExchangePrivateParameters(
            false,  // for client
            ((BCECPrivateKey) localEncPVK).engineGetKeyParameters(),
            ((BCECPrivateKey) localEphPVK).engineGetKeyParameters());
    CipherParameters pvkParamWithID = new ParametersWithID(sm2kePvkParam, GM_USERID);

    // 因为 BCECPublicKey.engineGetKeyParameters 没有公开,也可以用反射调用
    ECPublicKeyParameters peerPUKParam = new ECPublicKeyParameters(((BCECPublicKey) peerEncPUK).getQ(), ecDomainParameters);
    ECPublicKeyParameters peerEphPUKParam = new ECPublicKeyParameters(((BCECPublicKey) peerEphPUK).getQ(), ecDomainParameters);
    CipherParameters sm2kePukParam = new SM2KeyExchangePublicParameters(peerPUKParam, peerEphPUKParam);
    CipherParameters pukParamWithID = new ParametersWithID(sm2kePukParam, GM_USERID);// GM_USERID="1234567812345678".getBytes()

    SM2KeyExchange keyExchange = new SM2KeyExchange();
    keyExchange.init(pvkParamWithID);
    return keyExchange.calculateKey(GMConstants.ECDHE_KEY_BITS, pukParamWithID);   // pre-master key bits number, ECDHE_KEY_BITS=48*8
}

CertificateVerify认证签名问题

有个小问题,在双向认证的时候,发送CertificateVerify时,发送报文似乎只经过了hash没有经过SM2签名,可能会导致一些服务端验签失败,这个地方,此处按文档来说,应该是需要验签的,也可能代码里别的地方有只是我没看到

private void sendCertificateVerify() throws IOException {
        ProtocolVersion version = ProtocolVersion.NTLS_1_1;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for (Handshake handshake : handshakes) {
            out.write(handshake.getBytes());
        }

        // 此处只做了hash没有进行SM2签名
        byte[] signature = Crypto.hash(out.toByteArray());

        CertificateVerify cv = new CertificateVerify(signature);

        Handshake hs = new Handshake(Handshake.Type.CERTIFICATE_VERIFY, cv);
        Record rc = new Record(ContentType.HANDSHAKE, version, hs.getBytes());
        socket.recordStream.write(rc);
        handshakes.add(hs);
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.