Skip to content

Commit

Permalink
支持TrustedCAIndication扩展
Browse files Browse the repository at this point in the history
随机数时间支持外部时间源
  • Loading branch information
Trisia committed Jan 25, 2025
1 parent 8cbaf01 commit 4dfebdc
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 12 deletions.
43 changes: 43 additions & 0 deletions doc/ClientConfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,46 @@ config := &tlcp.Config{
},
}
```


### 2.5 选择授信CA证书

在 GM/T0024-2023《SSL VPN技术规范》中支持了Hello消息扩展字段,通过扩展字段中的TrustedCAKeys就可让服务器根据指定的证书信息选择合适的证书,
以实现多证书密钥的切换。

**注意:该扩展字段取决于服务端是否实现了证书匹配方法,若未实现参数无效!**

在客户端您可以通过下面方式指定需要CA证书:

```go
config := &tlcp.Config{
// ...
TrustedCAIndications: []TrustedAuthority{
{IdentifierType: tlcp.IdentifierTypeX509Name, Identifier: expectCertX509Name},
},
}
```

目前支持一下类型的证书信息参数:

| 参数名称 | 参数值 | 参数意义 |
| :-- | :-- |:----------------|
| IdentifierTypePreAgreed| 0 | Pre-agreed预先协商 |
| IdentifierTypeX509Name|2 | X.509证书名称 |
| IdentifierTypeKeySM3Hash|4 | 密钥SM3哈希 |
| IdentifierTypeCertSM3Hash|5 | 证书SM3哈希 |


### 2.6 验证服务端域名证书

在客户端您可以通过配置ServerName的方式开启校验服务端的域名证书,当服务端的域名证书与ServerName的域名不匹配时将会客户端将会抛出证书错误。

```go
config := &tlcp.Config{
// ...
ServerName: "www.example.com"
}
```

在配置了该项参数后客户端也会在ClientHello消息中附带SNI扩展供服务器根据域名选择证书,以此实现虚拟主机。(需要服务端支持)

47 changes: 47 additions & 0 deletions doc/ServerConfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,50 @@ config := &tlcp.Config{
},
}
```

### 2.6 根据TrustedCAKey扩展选择证书

在 GM/T0024-2023《SSL VPN技术规范》中支持了Hello消息扩展字段,通过扩展字段中的TrustedCAKeys就可让服务器根据指定的证书信息选择合适的证书。

目前支持一下类型的证书信息参数:

| 参数名称 | 参数值 | 参数意义 |
| :-- | :-- |:----------------|
| IdentifierTypePreAgreed| 0 | Pre-agreed预先协商 |
| IdentifierTypeX509Name|2 | X.509证书名称 |
| IdentifierTypeKeySM3Hash|4 | 密钥SM3哈希 |
| IdentifierTypeCertSM3Hash|5 | 证书SM3哈希 |


默认情况下GoTLCP将会忽略TrustedCAKeys扩展,您需要自己实现相应方法来实现证书的选择,主要为实现 `GetCertificate``GetKECertificate`方法


```go
config := &tlcp.Config{
// ...
GetCertificate: func(info *ClientHelloInfo) (*Certificate, error) {
if len(info.TrustedCAIndications) > 0 {
if info.TrustedCAIndications[0].IdentifierType == IdentifierTypeX509Name {
// 服务端根据客户端提供的CA指示选择签名证书
if bytes.Compare(info.TrustedCAIndications[0].Identifier, mySigCert.Leaf.RawSubject) == 0 {
return &mySigCert, nil
}
}
}
return &sigCert, nil
},
GetKECertificate: func(info *ClientHelloInfo) (*Certificate, error) {
if len(info.TrustedCAIndications) > 0 {
if info.TrustedCAIndications[0].IdentifierType == IdentifierTypeX509Name {
if bytes.Compare(info.TrustedCAIndications[0].Identifier, myEncCert.Leaf.RawSubject) == 0 {
return &myEncCert, nil
}
}
}
return &encCert, nil
},
}
```

以上示例展示如何通过 **X.509证书名称** 寻找证书返回证书方法。

13 changes: 13 additions & 0 deletions tlcp/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,11 @@ type ClientHelloInfo struct {
// SupportedVersions 客户端支持的TLCP版本,目前只有 0x0101
SupportedVersions []uint16

// TrustedCAIndications 客户端信任的CA列表
// 注意该参数为可选参数,在客户端发送TrustedAuthority扩展字段时才会存在。
// 服务端可以使用该参数选择合适的证书,做到证书的动态选择。
TrustedCAIndications []TrustedAuthority

// Conn 底层连接对象,请不要读写该对象,否则会导致TLCP连接异常
Conn net.Conn

Expand Down Expand Up @@ -434,6 +439,14 @@ type Config struct {
// 这个配置用于客户端使用ECDHE密码套件时与其他实现进行兼容,如果你在进行ECDHE密码套件的集成测试时失败,可以尝试配置这个变量。
// 默认当作structure,起始无两字节长度。
ClientECDHEParamsAsVector bool

// TrustedCAIndications 授信CA指示 Trusted CA Indications
// 该参数仅客户端使用,用于指定客户端信任的CA列表,ClientHello 扩展字段的方式发送。
// 服务端可选的从该列表中选择一张匹配的证书,以证书消息的方式发送。
// 注意:在GoTLCP默认不会使用该参数!
// 若需要在服务端识别并且使用该参数,请通过实现 GetCertificate 与 GetKECertificate 方法,
// 从 ClientHelloInfo 中获取扩展字段,然后根据扩展字段选择合适的证书。
TrustedCAIndications []TrustedAuthority
}

// Clone 复制一个新的连接配置对象
Expand Down
5 changes: 5 additions & 0 deletions tlcp/handshake_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, error) {
// 未指定时默认使用SM2
hello.supportedCurves = []CurveID{CurveSM2}
}
// 若用户指定了授信CA指示,则发送 trusted_ca_keys类型扩展
if len(config.TrustedCAIndications) > 0 {
// 发送扩展
hello.trustedAuthorities = config.TrustedCAIndications
}

hasAuthKeyPair := false
if len(config.Certificates) > 0 || config.GetClientCertificate != nil {
Expand Down
20 changes: 13 additions & 7 deletions tlcp/handshake_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,12 +763,13 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error {
func clientHelloInfo(ctx context.Context, c *Conn, clientHello *clientHelloMsg) *ClientHelloInfo {
supportedVers := supportedVersionsFromMax(clientHello.vers)
return &ClientHelloInfo{
CipherSuites: clientHello.cipherSuites,
ServerName: c.serverName,
SupportedVersions: supportedVers,
Conn: c.conn,
config: c.config,
ctx: ctx,
CipherSuites: clientHello.cipherSuites,
ServerName: c.serverName,
SupportedVersions: supportedVers,
TrustedCAIndications: clientHello.trustedAuthorities,
Conn: c.conn,
config: c.config,
ctx: ctx,
}
}

Expand All @@ -780,7 +781,12 @@ func (c *Conn) tlcpRand() ([]byte, error) {
if err != nil {
return nil, err
}
unixTime := time.Now().Unix()
var unixTime int64
if c.config.Time != nil {
unixTime = c.config.Time().Unix()
} else {
unixTime = time.Now().Unix()
}
rd[0] = uint8(unixTime >> 24)
rd[1] = uint8(unixTime >> 16)
rd[2] = uint8(unixTime >> 8)
Expand Down
112 changes: 109 additions & 3 deletions tlcp/handshake_server_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package tlcp

import (
"bytes"
"encoding/hex"
"fmt"
"net"
"testing"
Expand Down Expand Up @@ -349,15 +351,119 @@ func Test_HelloExt_SNI_ACK(t *testing.T) {
Certificates: []Certificate{sigCert, encCert},
Time: runtimeTime,
})
go func() {
defer svc.Close()
// 忽略服务端收到的错误
_ = svc.Handshake()
}()
if err := conn.Handshake(); err == nil {
t.Fatalf("Expect server name ack, and bad server alert, but not")
}
}

// 测试服务端授信CA指示选择证书
func Test_HelloExt_TrustedCAKeys(t *testing.T) {
sigKeyPEM := `-----BEGIN SM2 PRIVATE KEY-----
MHcCAQEEIF6AaiYOCERbLdmRrBpZSBUH09ZpMt4lb5RLmqbgQK3zoAoGCCqBHM9V
AYItoUQDQgAEL8yl88eP/iuFCHbGmUrQZiYvzKK7A9Oy+6wHGyaQrzuS/wdscqVk
oQNi5FuKlAVHVmVTjkjH7IU5p92rpHj1FQ==
-----END SM2 PRIVATE KEY-----`
sigCertPEM := `-----BEGIN CERTIFICATE-----
MIIBrDCCAVGgAwIBAgIEZ5RBdDAKBggqgRzPVQGDdTAfMQswCQYDVQQGEwJDTjEQ
MA4GA1UEAwwHVEVTVF9DQTAgFw0yNTAxMjIyMTMwMDBaGA8yMDU1MDEyNTAxNDIx
MlowIDELMAkGA1UEBhMCQ04xETAPBgNVBAMTCE15U2VydmVyMFkwEwYHKoZIzj0C
AQYIKoEcz1UBgi0DQgAEL8yl88eP/iuFCHbGmUrQZiYvzKK7A9Oy+6wHGyaQrzuS
/wdscqVkoQNi5FuKlAVHVmVTjkjH7IU5p92rpHj1FaN4MHYwDgYDVR0PAQH/BAQD
AgO4MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAfBgNVHSMEGDAWgBSn
TQSuuzywqoq/YQD9OcTW8kLtGzAkBgNVHREEHTAbgglsb2NhbGhvc3SCCHRlc3Qu
Y29thwR/AAABMAoGCCqBHM9VAYN1A0kAMEYCIQDckhsmNO29hz38NT+b3dv6rRsA
omGse9kjXdbbakmqXQIhAIL4imB58muF0QASb7aDMF57x7UPnUWlE2qB+u9xfxf+
-----END CERTIFICATE-----`
encKeyPEM := `-----BEGIN SM2 PRIVATE KEY-----
MHcCAQEEIApbU/lJmWDpqDkpkquTXhsl5EzEE1oI6rSVOzwEngdgoAoGCCqBHM9V
AYItoUQDQgAErY8lplH1HTupLnc7R423ZTSkaJxoZYyW48naf+2/gLr1rWifdZ4Q
TMgIEkiLj3riC1Ohyok9XW1iIeru4mRUtg==
-----END SM2 PRIVATE KEY-----`
encCertPEM := `-----BEGIN CERTIFICATE-----
MIIBqzCCAVGgAwIBAgIEZ5RBkjAKBggqgRzPVQGDdTAfMQswCQYDVQQGEwJDTjEQ
MA4GA1UEAwwHVEVTVF9DQTAgFw0yNTAxMjIyMTMwMDBaGA8yMDU1MDEyNTAxNDI0
MlowIDELMAkGA1UEBhMCQ04xETAPBgNVBAMTCE15U2VydmVyMFkwEwYHKoZIzj0C
AQYIKoEcz1UBgi0DQgAErY8lplH1HTupLnc7R423ZTSkaJxoZYyW48naf+2/gLr1
rWifdZ4QTMgIEkiLj3riC1Ohyok9XW1iIeru4mRUtqN4MHYwDgYDVR0PAQH/BAQD
AgO4MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAfBgNVHSMEGDAWgBSn
TQSuuzywqoq/YQD9OcTW8kLtGzAkBgNVHREEHTAbgglsb2NhbGhvc3SCCHRlc3Qu
Y29thwR/AAABMAoGCCqBHM9VAYN1A0gAMEUCIQDpsbE5nCaxcOy41ZEiYKA9Mdfi
uoaZZ9DpBTasGqCZPwIgEGvmLIB717Cn/3MD2Uqhxm2PqhGQ0hY4lo7J8BG5F4g=
-----END CERTIFICATE-----`
mySigCert, err := X509KeyPair([]byte(sigCertPEM), []byte(sigKeyPEM))
if err != nil {
t.Fatal(err)
}
myEncCert, err := X509KeyPair([]byte(encCertPEM), []byte(encKeyPEM))
if err != nil {
t.Fatal(err)
}

// cert subject: CN=MyServer,C=CN
expectCertX509Name, _ := hex.DecodeString("3020310b300906035504061302434e3111300f060355040313084d79536572766572")
//fmt.Println(hex.EncodeToString(expectCertX509Name))
//fmt.Println(hex.EncodeToString(mySigCert.Leaf.RawSubject))
//fmt.Println(hex.EncodeToString(myEncCert.Leaf.RawSubject))

cli, svr := tcpPipe(8454)
conn := Client(cli, &Config{
Time: runtimeTime,
RootCAs: simplePool,
TrustedCAIndications: []TrustedAuthority{
{IdentifierType: IdentifierTypeX509Name, Identifier: expectCertX509Name},
},
})
// 服务端通过GetCertificate和GetKECertificate选择证书
// 在实现中通过 TrustedCAIndications 选择证书
svc := Server(svr, &Config{
Time: runtimeTime,
GetCertificate: func(info *ClientHelloInfo) (*Certificate, error) {
if len(info.TrustedCAIndications) > 0 {
if info.TrustedCAIndications[0].IdentifierType == IdentifierTypeX509Name {
// 服务端根据客户端提供的CA指示选择签名证书
if bytes.Compare(info.TrustedCAIndications[0].Identifier, mySigCert.Leaf.RawSubject) == 0 {
return &mySigCert, nil
}
}
}
return &sigCert, nil
},
GetKECertificate: func(info *ClientHelloInfo) (*Certificate, error) {
if len(info.TrustedCAIndications) > 0 {
if info.TrustedCAIndications[0].IdentifierType == IdentifierTypeX509Name {
if bytes.Compare(info.TrustedCAIndications[0].Identifier, myEncCert.Leaf.RawSubject) == 0 {
return &myEncCert, nil
}
}
}
return &encCert, nil
},
})
go func() {
defer svc.Close()
err := svc.Handshake()
if err != nil {
t.Fatal(err)
}
}()
if err := conn.Handshake(); err == nil {
t.Fatalf("Expect server name ack, and bad server alert, but not")
if err = conn.Handshake(); err != nil {
t.Fatal(err)
}
certificates := conn.PeerCertificates()
if len(certificates) == 0 {
t.Fatalf("Expect get peer cert, but not")
}
actualSubject := certificates[0].RawSubject
if bytes.Compare(actualSubject, expectCertX509Name) != 0 {
t.Fatalf("Expect peer sign cert subject %02x ,but get %02x", expectCertX509Name, actualSubject)
}
actualSubject = certificates[1].RawSubject
if bytes.Compare(actualSubject, expectCertX509Name) != 0 {
t.Fatalf("Expect peer enc cert subject %02x ,but get %02x", expectCertX509Name, actualSubject)
}

}
19 changes: 17 additions & 2 deletions tlcp/handshake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestGenSelfSignedCert(t *testing.T) {
SerialNumber: new(big.Int).SetInt64(time.Now().Unix()),
Issuer: rootCert.Subject,
Subject: pkix.Name{Country: []string{"CN"}, Province: []string{"浙江"}, Locality: []string{"杭州"}, CommonName: "Entity_CERT"},
NotBefore: time.Now().Add(-time.Hour * 24),
NotBefore: runtimeTime().Add(-time.Hour * 24),
NotAfter: time.Now().AddDate(30, 0, 0),
KeyUsage: smx509.KeyUsageDigitalSignature | smx509.KeyUsageKeyEncipherment | smx509.KeyUsageDataEncipherment | smx509.KeyUsageKeyAgreement,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
Expand All @@ -191,6 +191,20 @@ func TestGenSelfSignedCert(t *testing.T) {
_ = pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: certificate})
}

// svrConn 让服务端监听器与连接绑定,以便在关闭连接时关闭监听器
type svrConn struct {
l net.Listener
net.Conn
}

func (s *svrConn) Close() error {
err := s.Conn.Close()
if s.l != nil {
return s.l.Close()
}
return err
}

func tcpPipe(port ...int) (cli net.Conn, svr net.Conn) {
addr := ""
if len(port) > 0 {
Expand All @@ -209,7 +223,8 @@ func tcpPipe(port ...int) (cli net.Conn, svr net.Conn) {
if err != nil {
return
}
svr = conn
// 服务端监听器与连接绑定,以便在关闭连接时关闭监听器
svr = &svrConn{Conn: conn, l: listen}
}()
go func() {
defer wg.Done()
Expand Down

0 comments on commit 4dfebdc

Please sign in to comment.