go x/crypto/acme/autocert: 支持仅在TLS1.3连接上使用ECDSA

e7arh2l6  于 4个月前  发布在  Go
关注(0)|答案(5)|浏览(48)

最新版本的x/crypto/acme/autocert在编写时为:
https://pkg.go.dev/golang.org/x/crypto@v0.0.0-20211215153901-e495a2d5b3d3/acme/autocert

你做了什么?

调用Manager.GetCertificate以强制立即检索丢失的证书(在应用程序启动期间)。

你会看到什么?

我期望autocert获取一个ECDSA证书。

你看到的是什么?

实际上,我第一次连接到我的服务(使用标准的工具,如curl和openssl s_client)时,获取了一个ECDSA证书。因为这些工具通常更倾向于使用ECDSA而不是RSA。

分析

我的初始tls.ClientHelloInfo只设置了ServerName,没有设置ciphersuites、版本等。这(正确地)在以下位置失败了supportsECDSA检查: https://cs.opensource.google/go/x/crypto/+/e495a2d5:acme/autocert/autocert.go;drc=e495a2d5b3d3be43468d0ebb413f46eeaedf7eb3;l=322
这是公平的,所以我尝试了一个仅支持ECDSA的hello:

tryGetCertificate := func(name string) {
	hello := &tls.ClientHelloInfo{
		ServerName: name,

		CipherSuites: []uint16{tls.TLS_AES_128_GCM_SHA256},
		SupportedCurves: []tls.CurveID{tls.CurveP256},
		SignatureSchemes: []tls.SignatureScheme{tls.ECDSAWithP256AndSHA256},
		SupportedVersions: []uint16{tls.VersionTLS13},
	}
	m.GetCertificate(hello)
}

然而,这仍然导致获取到了一个RSA证书:supportsECDSA没有考虑到TLS1.3配置,需要一个支持TLS <= 1.2的ECDSA签名方案。参见: https://cs.opensource.google/go/x/crypto/+/e495a2d5:acme/autocert/autocert.go;l=353;drc=e495a2d5b3d3be43468d0ebb413f46eeaedf7eb3
背景信息:在TLS1.3中,签名方案已经被从CipherSuite定义中提取/分离出来。
我认为supportsECDSA应该更新以识别TLS1.3,并在SignatureSchemes中检查ECDSA。
此外,我认为supportsECDSA应该修改为同时检查ClientHelloInfo中的RSA支持:如果客户端无法使用它,那么获取RSA证书是没有意义的。我知道RSA曾经是默认的签名方案。但未来可能不再是这样,客户端已经可以自由选择他们希望使用的签名方案。
我可以通过简单地将tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256包含在CipherSuites切片中来解决我当前的问题(强制获取一个ECDSA证书)。然后它通过了supportsECDSA检查。但是实际的TLS1.3-only连接仍然会获取一个RSA证书,而不是一个ECDSA证书,即使客户端不支持RSA。
如果这听起来合理,我可以写一个补丁。
另一个想法:我们可以将Manager.GetCertificate更改为在不存在ECDSA证书的情况下接受现有的RSA证书。我们必须小心,支持RSA使用的密码套件不会比ECDSA密码套件弱。这就是为什么我不确定这是否值得。但是,如果我们将签名方案兼容性扩展到检查RSA,也许它是值得的。

efzxgjgh

efzxgjgh1#

@FiloSottile,你能看一下这个吗?

8ehkhllq

8ehkhllq2#

/cc @golang/security

46scxncf

46scxncf3#

我对此进行了进一步的研究。我尝试验证我确实会因为仅使用ECDSA而导致TLS1.3连接出错。我想配置一个仅使用ECDSA作为签名方案的tls.Config。看起来tls.Config似乎不允许设置签名方案。我不知道这是否是由于TLS1.3规范,还是Go实现的原因。我确实注意到了关于CipherSuites的注解:“请注意,TLS 1.3的ciphersuites不可配置。”我知道现代加密的目标是减少选项/协商。也许对于TLS1.3,始终需要支持RSA。如果是这样的话,那么这个问题的标题声明是不正确的(我是从我看到的autocert推断出来的)。至少很明显,我对TLS1.3的知识是不足的。
但是还有更多。我编写了这个程序来连接到我的autocert服务器。为了查看tls.ClientHelloInfo是什么样子,以及连接是否成功:

package main

import (
        "crypto/tls"
        "log"
        "os"
)

func main() {
        log.SetFlags(0)
        addr := os.Args[1]

        config := &tls.Config{
                MinVersion: tls.VersionTLS13,
        }
        if _, err := tls.Dial("tcp", addr, config); err != nil {
                log.Fatalf("dial: %s", err)
        }
        log.Printf("connected")
}

它导致了这个tls.ClientHelloInfo:

&tls.ClientHelloInfo{
	CipherSuites:[]uint16{0xc02b, 0xc02f, 0xc02c, 0xc030, 0xcca9, 0xcca8, 0xc009, 0xc013, 0xc00a, 0xc014, 0x9c, 0x9d, 0x2f, 0x35, 0xc012, 0xa, 0x1301, 0x1302, 0x1303},
	ServerName:"host.example",
	SupportedCurves:[]tls.CurveID{0x1d, 0x17, 0x18, 0x19},
	SupportedPoints:[]uint8{0x0},
	SignatureSchemes:[]tls.SignatureScheme{0x804, 0x403, 0x807, 0x805, 0x806, 0x401, 0x501, 0x601, 0x503, 0x603, 0x201, 0x203},
	SupportedProtos:[]string(nil),
	SupportedVersions:[]uint16{0x304},
...

我很惊讶TLS <=1.2的密码套件会出现在仅用于TLS1.3的连接列表中。我在go1.17.6和go1.18beta1上进行了测试,结果相同。我以为可能是crypto/tls服务器插入了它们。
为了验证这一点,我尝试使用不同的生态系统:curl --tlsv1.3 https://host.example
那次没有出现TLS<=1.2的密码套件。所以看起来crypto/tls客户端在仅用于TLS1.3的连接中插入了TLS<=1.2的密码套件。这导致了autocert中的supportsECDSA检查成功。curl发出的请求导致supportsECDSA返回false,因此(成功的)连接使用了RSA,而不是ECDSA。
在上面的ClientHelloInfo中,签名方案是不同的。所以我得出结论,TLS1.3中的签名方案是可以配置的。但是RSA支持可能始终是必需的,“仅使用ECDSA的TLS1.3连接”可能并不存在。
参考资料:这里使用的是我用于测试的acmecert服务器:https://github.com/letsencrypt/pebble ;以及go.mod:

module acmecert

go 1.17

require golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3

require (
        golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
        golang.org/x/text v0.3.6 // indirect
)
7y4bm7vi

7y4bm7vi4#

ECDSA TLS1.3连接确实失败了:

$ openssl s_client -tls1_3 -connect host.example:443 -sigalgs "ECDSA+SHA256"
CONNECTED(00000004)
139741787190656:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../ssl/record/rec_layer_s3.c:1543:SSL alert number 40
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 215 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

从acmecert记录的日志如下(根据当前的支持检测,在"supportsECDSA"中添加了一条额外的日志行):

hello:
&tls.ClientHelloInfo{
        CipherSuites:[]uint16{0x1302, 0x1303, 0x1301, 0xff},
        ServerName:"host.example",
        SupportedCurves:[]tls.CurveID{0x1d, 0x17, 0x1e, 0x19, 0x18},
        SupportedPoints:[]uint8{0x0, 0x1, 0x2},
        SignatureSchemes:[]tls.SignatureScheme{0x403},
        SupportedProtos:[]string(nil),
        SupportedVersions:[]uint16{0x304},
...
}
client supports ECDSA: false
http: TLS handshake error from 0.0.0.0:44898: tls: peer doesn't support any of the certificate's signature algorithms
w8f9ii69

w8f9ii695#

支持ECDSA确实是一个非常有限和部分的检查。假设RSA始终是安全的后备,这并不一定是正确的。我在Go 1.14中添加了ClientHelloInfo.SupportsCertificate来解决这个问题。它已经在标准库中存在足够长的时间了,我们应该将其切换到autocert并删除supportsECDSA。

相关问题