英文:
Issues with TLS connection in Golang
问题
我有以下的证书层次结构:
根证书-->CA-->3个叶子证书
整个证书链都明确定义了serverAuth和clientAuth作为扩展密钥用途。
在我的Go代码中,我像这样创建了一个tls.Config对象:
func parseCert(certFile, keyFile string) (cert tls.Certificate, err error) {
certPEMBlock , err := ioutil.ReadFile(certFile)
if err != nil {
return
}
var certDERBlock *pem.Block
for {
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
if certDERBlock == nil {
break
}
if certDERBlock.Type == "CERTIFICATE" {
cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
}
}
// 需要翻转数组,因为openssl给出的格式与golang tls期望的格式相反。
cpy := make([][]byte, len(cert.Certificate))
copy(cpy, cert.Certificate)
var j = 0
for i := len(cpy)-1; i >=0; i-- {
cert.Certificate[j] = cert.Certificate[i]
j++
}
keyData, err := ioutil.ReadFile(keyFile)
if err != nil {
return
}
block, _ := pem.Decode(keyData)
if err != nil {
return
}
ecdsaKey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return
}
cert.PrivateKey = ecdsaKey
return
}
// 使用提供的证书、密钥和CA证书文件配置并创建一个tls.Config实例。
func configureTLS(certFile, keyFile, caCertFile string) (tlsConfig *tls.Config, err error) {
c, err := parseCert(certFile, keyFile)
if err != nil {
return
}
ciphers := []uint16 {
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
}
certPool := x509.NewCertPool()
buf, err := ioutil.ReadFile(caCertFile)
if nil != err {
log.Println("failed to load ca cert")
log.Fatal(seelog.Errorf("failed to load ca cert.\n%s", err))
}
if !certPool.AppendCertsFromPEM(buf) {
log.Fatalln("Failed to parse truststore")
}
tlsConfig = &tls.Config {
CipherSuites: ciphers,
ClientAuth: tls.RequireAndVerifyClientCert,
PreferServerCipherSuites: true,
RootCAs: certPool,
ClientCAs: certPool,
Certificates: []tls.Certificate{c},
}
return
}
certFile是证书链文件,keyFile是私钥文件。caCertFile是信任库,只包含根证书。
所以基本上,我希望在这个函数返回的tls.Config对象中包含以下内容:
RootCAs:来自caCertFile的根证书
ClientCAs:同样来自caCertFile的根证书,与RootCAs相同
Certificates:一个包含certFile中所有证书的单个证书链,按照从叶子到根的顺序排列。
现在,我有3个部分:一个服务器、一个中继和一个客户端。客户端直接连接到中继,中继将请求转发给服务器。所有三个部分都使用相同的配置代码,当然使用不同的证书/密钥。caCertFile在所有3个部分之间是相同的。
现在,如果我启动服务器和中继,并从浏览器连接到中继,一切都正常,所以我可以假设中继和服务器之间的连接是正常的。问题出现在我尝试将客户端连接到中继时。当我这样做时,TLS握手失败,并返回以下错误:
x509: 由未知授权机构签名的证书
在中继方面,我得到以下错误:
http: 来自<客户端IP已隐藏>的TLS握手错误: 远程错误: 错误的证书
我真的不知所措。显然我设置了一些错误,但我不确定是什么。很奇怪的是,它从浏览器中工作(这意味着从中继到服务器的配置是正确的),但是在我的客户端上使用相同的配置却不起作用。
更新:
如果我在中继和客户端的tls.Config对象中添加InsecureSkipVerify: true
,错误会变成:
在客户端上:远程错误: 错误的证书
在中继上:http: 来自<IP>的TLS握手错误: tls: 客户端未提供证书
所以看起来客户端由于某种原因拒绝了来自服务器(中继)的证书,因此从未向服务器(中继)发送其证书。
我真希望Go有更好的日志记录。我甚至无法接入这个过程来查看到底发生了什么。
英文:
I have the following certificate hierarchy:
Root-->CA-->3 leaf certificates
The entire chain has both serverAuth and clientAuth as extended key usages explicitly defined.
In my go code, I create a tls.Config object like so:
func parseCert(certFile, keyFile string) (cert tls.Certificate, err error) {
certPEMBlock , err := ioutil.ReadFile(certFile)
if err != nil {
return
}
var certDERBlock *pem.Block
for {
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
if certDERBlock == nil {
break
}
if certDERBlock.Type == "CERTIFICATE" {
cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
}
}
// Need to flip the array because openssl gives it to us in the opposite format than golang tls expects.
cpy := make([][]byte, len(cert.Certificate))
copy(cpy, cert.Certificate)
var j = 0
for i := len(cpy)-1; i >=0; i-- {
cert.Certificate[j] = cert.Certificate[i]
j++
}
keyData, err := ioutil.ReadFile(keyFile)
if err != nil {
return
}
block, _ := pem.Decode(keyData)
if err != nil {
return
}
ecdsaKey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return
}
cert.PrivateKey = ecdsaKey
return
}
// configure and create a tls.Config instance using the provided cert, key, and ca cert files.
func configureTLS(certFile, keyFile, caCertFile string) (tlsConfig *tls.Config, err error) {
c, err := parseCert(certFile, keyFile)
if err != nil {
return
}
ciphers := []uint16 {
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
}
certPool := x509.NewCertPool()
buf, err := ioutil.ReadFile(caCertFile)
if nil != err {
log.Println("failed to load ca cert")
log.Fatal(seelog.Errorf("failed to load ca cert.\n%s", err))
}
if !certPool.AppendCertsFromPEM(buf) {
log.Fatalln("Failed to parse truststore")
}
tlsConfig = &tls.Config {
CipherSuites: ciphers,
ClientAuth: tls.RequireAndVerifyClientCert,
PreferServerCipherSuites: true,
RootCAs: certPool,
ClientCAs: certPool,
Certificates: []tls.Certificate{c},
}
return
}
certFile is the certificate chain file and keyFile is the private key file. caCertFile is the truststore and consists of just the root certificate
So basically, here is what I expect to have inside of my tls.Config object that comes out of this function:
RootCAs: Just the root certificate from caCertFile
ClientCAs: Again, just the root certificate from caCertFile, same as RootCAs
Certificates: A single certificate chain, containing all of the certificates in certFile, ordered to be leaf first.
Now, I have 3 pieces here. A server, a relay, and a client. The client connects directly to the relay, which in turn forwards the request to the server. All three pieces use the same configuration code, of course using different certs/keys. The caCertFile is the same between all 3 pieces.
Now, if I stand up the server and the relay and connect to the relay from my browser, all goes well, so I can assume that the connection between relay and server is fine. The issue comes about when I try to connect my client to the relay. When I do so, the TLS handshake fails and the following error is returned:
x509: certificate signed by unknown authority
On the relay side of things, I get the following error:
http: TLS handshake error from <client ip redacted>: remote error: bad certificate
I am really at a loss here. I obviously have something setup incorrectly, but I am not sure what. It's really weird that it works from the browser (meaning that the config is correct from relay to server), but it doesn't work with the same config from my client.
Update:
So if I add InsecureSkipVerify: true
to my tls.Config object on both the relay and the client, the errors change to:
on the client: remote error: bad certificate
and on the relay: http: TLS handshake error from <ip>: tls: client didn't provide a certificate
So it looks like the client is rejecting the certificate on from the server (the relay) due to it being invalid for some reason and thus never sending its certificate to the server (the relay).
I really wish go had better logging. I can't even hook into this process to see what, exactly, is going on.
答案1
得分: 3
当你说
> 需要翻转数组,因为openssl给我们的格式与golang tls期望的格式相反。
我使用openssl生成的证书,并没有问题地使用以下代码打开它们:
tls.LoadX509KeyPair(cert, key)
无论如何,错误消息<b>bad certificate</b>是由于服务器无法将客户端提供的证书与其RootCAs匹配。我在Go中使用自签名证书时也遇到过这个问题,唯一的解决方法是将<i>caCertFile</i>安装到机器的系统证书中,并使用x509.SystemCertPool()
而不是x509.NewCertPool()
。
也许其他人会有其他解决方案?
英文:
When you say
> Need to flip the array because openssl gives it to us in the opposite format than golang tls expects.
I have used certificates generated by openssl and had no problem opening them with:
tls.LoadX509KeyPair(cert, key)
Anyway, the error message <b>bad certificate</b> is due to the server not managing to match the client-provided certificate against its RootCAs. I have also had this problem in Go using self-signed certificats and the only work-around I've found is to install the <i>caCertFile</i> into the machines system certs, and use x509.SystemCertPool()
instead of x509.NewCertPool()
.
Maybe someone else will have another solution?
答案2
得分: 0
除了beldin0建议的之外,我尝试了另一种方法来做这个。
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(crt)
client := &http.Client{
//一些配置
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
},
},
}
这里,变量"crt"是您证书中的内容。
基本上,您只需将其添加到您的代码中(或作为配置文件读取)。
然后一切都会正常运行。
英文:
Beside what beldin0 suggested.
I have tried another way to do this.
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(crt)
client := &http.Client{
//some config
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
},
},
}
Here, the variable "crt" is the content in your certificate.
Basically, you just add it into your code(or read as a config file).
Then everything would be fine.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论