Golang中x509 mTLS实现:客户端配置InsecureSkipVerify为false且服务端要求验证客户端证书(使用不同证书密钥对)
Golang中x509 mTLS实现:客户端配置InsecureSkipVerify为false且服务端要求验证客户端证书(使用不同证书密钥对) 我正在尝试为客户端和服务器端的 tls.Config 找到正确的配置,以满足以下约束条件:
- 我们从一个 CA 生成独立的证书——一个用于客户端,另一个完全不同的用于服务器。
- 证书是使用 golang 的 x509.CreateCertificate 生成的——而不是 openssl。
- 客户端的 tls.Config 必须 指定 InsecureSkipVerify 为 false。
- 服务器的 tls.Config 必须 指定 ClientAuth 为 RequireAndVerifyClientCert。
我一直无法生成满足这组约束条件并允许建立连接的证书。我最接近成功的情况是,当我在客户端和服务器端都使用 CA 证书时——只有在这种情况下,客户端和服务器之间的一切才能正常工作。
如果我生成一个 CA 证书,并用它来创建并签名服务器证书,然后生成一个由该 CA 证书签名的客户端证书——我会遇到以下错误:
连接失败:x509: 证书由未知机构签名(可能因为尝试验证候选机构证书“localhost”时出现“crypto/rsa: 验证错误”)。
如果客户端使用 CA 证书连接到服务器(当服务器使用由 CA 生成并签名的证书时),也会出现同样的错误。
当我让服务器使用 CA 证书作为服务器证书,而客户端使用由 CA 生成并签名的证书时,在 Dial 之后,当我尝试在连接上读取数据时会失败,错误信息是:
连接读取错误:远程错误:tls: 错误的证书
只有当服务器和客户端使用完全相同的证书时,客户端和服务器才能连接并交换数据。
欢迎提出任何想法和建议。如果有人想深入研究并看一看,我可以发布一个 git 仓库。提前说明——我这样做是为了演示使用动态 tls.Config 进行动态证书生成,以及使用自定义验证器通过 GetConfigForClient 和 VerifyPeerCertificate 进行验证。
更多关于Golang中x509 mTLS实现:客户端配置InsecureSkipVerify为false且服务端要求验证客户端证书(使用不同证书密钥对)的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你是否已将自定义的CA证书添加到RootCAs(在tls.Config中)或系统信任存储中?
rabarar: 使用自定义验证器的 VerifyPeerCertificate。
请注意,这仅在常规验证(主机名、信任链等)成功后才会被调用。
更多关于Golang中x509 mTLS实现:客户端配置InsecureSkipVerify为false且服务端要求验证客户端证书(使用不同证书密钥对)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
要实现满足你所有约束条件的mTLS配置,需要正确配置证书链和信任关系。以下是完整的示例代码:
服务端配置
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"net"
"net/http"
"time"
)
// 生成CA证书
func generateCA() (*x509.Certificate, *rsa.PrivateKey, error) {
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
caTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"My CA"},
CommonName: "My Root CA",
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 2,
}
caCertDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
if err != nil {
return nil, nil, err
}
caCert, err := x509.ParseCertificate(caCertDER)
if err != nil {
return nil, nil, err
}
return caCert, caKey, nil
}
// 使用CA签发证书
func signCertificate(caCert *x509.Certificate, caKey *rsa.PrivateKey, template *x509.Certificate) (*x509.Certificate, *rsa.PrivateKey, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
if err != nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(certDER)
if err != nil {
return nil, nil, err
}
return cert, key, nil
}
func startServer() {
// 生成CA
caCert, caKey, err := generateCA()
if err != nil {
log.Fatal(err)
}
// 生成服务器证书
serverTemplate := &x509.Certificate{
SerialNumber: big.NewInt(2),
Subject: pkix.Name{
Organization: []string{"My Server"},
CommonName: "server.example.com",
},
DNSNames: []string{"server.example.com", "localhost"},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
serverCert, serverKey, err := signCertificate(caCert, caKey, serverTemplate)
if err != nil {
log.Fatal(err)
}
// 生成客户端证书
clientTemplate := &x509.Certificate{
SerialNumber: big.NewInt(3),
Subject: pkix.Name{
Organization: []string{"My Client"},
CommonName: "client.example.com",
},
DNSNames: []string{"client.example.com"},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
clientCert, _, err := signCertificate(caCert, caKey, clientTemplate)
if err != nil {
log.Fatal(err)
}
// 创建证书池并添加CA证书
caCertPool := x509.NewCertPool()
caCertPool.AddCert(caCert)
// 创建服务器TLS配置
serverTLSConfig := &tls.Config{
Certificates: []tls.Certificate{
{
Certificate: [][]byte{serverCert.Raw},
PrivateKey: serverKey,
},
},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: caCertPool,
MinVersion: tls.VersionTLS12,
}
// 创建HTTP处理器
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
fmt.Fprintf(w, "Hello, %s! Your certificate was verified.\n",
r.TLS.PeerCertificates[0].Subject.CommonName)
} else {
fmt.Fprintln(w, "Hello, anonymous!")
}
})
// 启动服务器
server := &http.Server{
Addr: ":8443",
Handler: handler,
TLSConfig: serverTLSConfig,
}
fmt.Println("Server starting on :8443")
log.Fatal(server.ListenAndServeTLS("", ""))
}
func main() {
startServer()
}
客户端配置
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"io/ioutil"
"log"
"math/big"
"net/http"
"time"
)
func startClient() {
// 注意:在实际应用中,客户端应该从安全存储中获取CA证书
// 这里为了演示,我们重新生成相同的CA
caCert, caKey, err := generateCA()
if err != nil {
log.Fatal(err)
}
// 生成客户端证书(与服务器端生成的相同)
clientTemplate := &x509.Certificate{
SerialNumber: big.NewInt(3),
Subject: pkix.Name{
Organization: []string{"My Client"},
CommonName: "client.example.com",
},
DNSNames: []string{"client.example.com"},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
clientCert, clientKey, err := signCertificate(caCert, caKey, clientTemplate)
if err != nil {
log.Fatal(err)
}
// 创建证书池并添加CA证书
caCertPool := x509.NewCertPool()
caCertPool.AddCert(caCert)
// 创建客户端TLS配置
clientTLSConfig := &tls.Config{
Certificates: []tls.Certificate{
{
Certificate: [][]byte{clientCert.Raw},
PrivateKey: clientKey,
},
},
RootCAs: caCertPool, // 关键:必须设置RootCAs
InsecureSkipVerify: false, // 必须为false
ServerName: "server.example.com", // 必须匹配服务器证书的CN或SAN
}
// 创建HTTP客户端
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: clientTLSConfig,
},
}
// 发起请求
resp, err := client.Get("https://localhost:8443/")
if err != nil {
log.Fatal("请求失败:", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal("读取响应失败:", err)
}
fmt.Println("服务器响应:", string(body))
}
func main() {
startClient()
}
关键点说明
-
证书链配置:
- 服务器需要配置
ClientCAs包含CA证书 - 客户端需要配置
RootCAs包含相同的CA证书
- 服务器需要配置
-
证书扩展用途:
- 服务器证书必须包含
x509.ExtKeyUsageServerAuth - 客户端证书必须包含
x509.ExtKeyUsageClientAuth
- 服务器证书必须包含
-
服务器名称验证:
- 客户端必须设置
ServerName匹配服务器证书的CN或SAN - 服务器证书的SAN必须包含客户端连接使用的地址
- 客户端必须设置
-
证书序列化(如果需要保存到文件):
// 保存证书到PEM文件
func saveCertToFile(cert *x509.Certificate, filename string) error {
certOut, err := os.Create(filename)
if err != nil {
return err
}
defer certOut.Close()
if err := pem.Encode(certOut, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
}); err != nil {
return err
}
return nil
}
// 保存私钥到PEM文件
func saveKeyToFile(key *rsa.PrivateKey, filename string) error {
keyOut, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer keyOut.Close()
if err := pem.Encode(keyOut, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
}); err != nil {
return err
}
return nil
}
这个配置满足你所有的约束条件:使用不同的证书密钥对、InsecureSkipVerify为false、ClientAuth为RequireAndVerifyClientCert,并且完全使用Go的x509库生成证书。


