使用Golang crypto生成的证书无法连接MQTTs服务器的问题排查
问题分析与解决方案
你的问题核心是Go标准库x509生成自签名证书时,默认不会自动添加X509v3 Authority Key Identifier和Subject Key Identifier扩展,而很多MQTT服务器(如Thingsboard)会要求证书包含这些扩展才能完成TLS握手验证。OpenSSL生成自签名证书时会自动添加这些扩展,因此能正常连接。
解决步骤
需要手动给证书模板添加这两个扩展,同时补充必要的KeyUsage和ExtKeyUsage字段,确保证书符合MQTT客户端的使用要求。
修改证书生成代码
调整GenerateCertificate函数中的x509.Certificate模板部分,添加关键扩展字段:
func GenerateCertificate(certificatePath string, keyPath string) error { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { // 修复原代码忽略密钥生成错误的问题 return err } serialNumber, err := rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil)) if err != nil { return err } now := time.Now() // 生成Subject Key Identifier(SKI) pubKeyBytes, err := x509.MarshalPKIXPublicKey(&key.PublicKey) if err != nil { return err } pubKeyInfo, err := x509.ParsePKIXPublicKey(pubKeyBytes) if err != nil { return err } ski, err := x509.SubjectKeyIdentifier(pubKeyInfo) if err != nil { return err } template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: "localhost", Country: []string{"AU"}, Organization: []string{"Internet Widgits Pty Ltd"}, Province: []string{"Some-State"}, }, Issuer: pkix.Name{ CommonName: "localhost", Country: []string{"AU"}, Organization: []string{"Internet Widgits Pty Ltd"}, Province: []string{"Some-State"}, }, NotBefore: now, NotAfter: now.AddDate(10, 0, 0), PublicKeyAlgorithm: x509.ECDSA, SignatureAlgorithm: x509.ECDSAWithSHA256, // 添加SKI扩展 SubjectKeyIdentifier: ski, // 添加AKI扩展(自签名证书复用SKI值) AuthorityKeyIdentifier: ski, // 补充密钥用途,确保可用于数字签名 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, // 指定证书用于客户端TLS认证 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) if err != nil { // 修复原代码忽略证书生成错误的问题 return err } // 后续文件写入逻辑保持不变 var certificateOut, keyOut io.Writer if _, e := os.Stat(keyPath); os.IsNotExist(e) { if _, err = os.Create(keyPath); err != nil { return err } } keyOut, err = os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } if _, e := os.Stat(certificatePath); os.IsNotExist(e) { if _, err = os.Create(certificatePath); err != nil { return err } } certificateOut, err = os.OpenFile(certificatePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } secp256r1, err := asn1.Marshal(asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}) if err != nil { return err } paramBlock := pem.EncodeToMemory(&pem.Block{Type: "EC PARAMETERS", Bytes: secp256r1}) if _, err := keyOut.Write(paramBlock); err != nil { return err } b, err := x509.MarshalECPrivateKey(key) privBlock := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}) if _, err := keyOut.Write(privBlock);err != nil { return err } certBlock := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) certificateOut.Write(certBlock) return nil }
关键修改说明
添加SKI与AKI扩展:
- SKI通过公钥生成,用于唯一标识证书的公钥;自签名证书的AKI直接复用SKI值,因为颁发者就是证书本身。
- 这两个扩展是很多TLS服务器验证证书链完整性的必要条件。
补充密钥用途字段:
KeyUsageDigitalSignature确保证书可用于身份签名,符合MQTT客户端认证的核心需求;ExtKeyUsageClientAuth明确标记证书的客户端TLS认证用途,避免服务器因证书用途不符拒绝连接。
修复错误处理:
原代码忽略了ecdsa.GenerateKey和x509.CreateCertificate的错误返回,添加检查后能提前发现生成过程中的问题。
验证修改效果
生成证书后,用以下命令查看证书扩展信息:
openssl x509 -in cert.pem -text -noout
确认输出中包含以下内容:
X509v3 Subject Key Identifier: [你的SKI哈希值] X509v3 Authority Key Identifier: keyid:[你的SKI哈希值] X509v3 Key Usage: Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Client Authentication
此时再使用Go生成的证书连接MQTTs服务器,即可正常完成TLS握手。
内容的提问来源于stack exchange,提问作者shiyue
相关产品推荐
相关产品推荐

