You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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
}

关键修改说明

  1. 添加SKI与AKI扩展:

    • SKI通过公钥生成,用于唯一标识证书的公钥;自签名证书的AKI直接复用SKI值,因为颁发者就是证书本身。
    • 这两个扩展是很多TLS服务器验证证书链完整性的必要条件。
  2. 补充密钥用途字段:

    • KeyUsageDigitalSignature确保证书可用于身份签名,符合MQTT客户端认证的核心需求;
    • ExtKeyUsageClientAuth明确标记证书的客户端TLS认证用途,避免服务器因证书用途不符拒绝连接。
  3. 修复错误处理:
    原代码忽略了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 09:58:13