如何在Golang中无私钥生成X.509证书签名请求(CSR)?
在Golang中使用远程私钥生成CSR的解决方案
问题背景
需要生成X.509证书签名请求(CSR),但私钥存储在远程安全服务中,无法本地持有。仅能通过API获取PEM编码的公钥,以及通过API调用远程私钥对数据进行签名。尝试用虚拟私钥生成CSR后修改x509.CertificateRequest的PublicKey和Signature字段,但原始ASN.1数据(Raw字段)未更新,无法得到正确的CSR。
核心原因
x509.CertificateRequest的Raw字段是解析CSR时缓存的原始ASN.1字节,修改结构体的其他字段不会自动重新生成Raw数据,且Go标准库未提供重新序列化该结构体的方法。因此必须手动构造CSR的ASN.1结构。
解决方案:手动构造CSR的ASN.1结构
CSR遵循ASN.1 DER编码标准,结构分为三部分:待签名数据(TBSCertRequest)、签名算法、签名值。我们可以手动构造各部分并组合成完整CSR,步骤如下:
- 解析远程获取的公钥,将其序列化为
SubjectPublicKeyInfo格式的ASN.1数据。 - 构造待签名数据(TBSCertRequest),包含CSR版本、主题信息、公钥、扩展属性(如邮箱)。
- 调用远程API签名TBS数据,获取符合指定算法的签名值。
- 组合完整CSR结构,序列化后编码为PEM格式。
完整代码示例
package main import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "fmt" "os" ) var oidEmailAddress = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1} // 定义CSR对应的ASN.1结构体 type certificateRequest struct { TBSCertRequest tbsCertRequest SignatureAlgorithm pkix.AlgorithmIdentifier Signature asn1.BitString } type tbsCertRequest struct { Version int `asn1:"optional,explicit,tag:0"` Subject pkix.Name SubjectPKInfo pkix.RawSubjectPublicKeyInfo Attributes []pkix.AttributeTypeAndValueSET `asn1:"optional,explicit,tag:1"` } // 替换为实际的远程签名API调用 func remoteSign(tbsData []byte) ([]byte, error) { // 注意:需根据签名算法要求处理数据,例如SHA256WithRSA需先计算SHA256哈希再签名 // 此处为示例,实际应调用远程私钥服务获取签名 return []byte("replace-with-actual-signature-from-api"), nil } func main() { // 1. 解析远程获取的公钥PEM pubKeyPEM := []byte(`-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+VvZ6hXtGJ2e7QkL+F5aSf8L ... 替换为实际公钥内容 ... -----END PUBLIC KEY-----`) block, _ := pem.Decode(pubKeyPEM) if block == nil || block.Type != "PUBLIC KEY" { fmt.Println("Failed to parse public key PEM") return } pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { fmt.Printf("Parse public key error: %v\n", err) return } // 2. 将公钥序列化为SubjectPublicKeyInfo格式 spkiBytes, err := x509.MarshalPKIXPublicKey(pubKey) if err != nil { fmt.Printf("Marshal public key error: %v\n", err) return } var spki pkix.RawSubjectPublicKeyInfo if _, err := asn1.Unmarshal(spkiBytes, &spki); err != nil { fmt.Printf("Unmarshal SPKI error: %v\n", err) return } // 3. 构造CSR主题信息 subj := pkix.Name{ CommonName: "example.com", Country: []string{"AU"}, Province: []string{"Some-State"}, Locality: []string{"MyCity"}, Organization: []string{"Company Ltd"}, OrganizationalUnit: []string{"IT"}, } // 构造扩展属性(如邮箱) attributes := []pkix.AttributeTypeAndValueSET{ { Type: oidEmailAddress, Value: []pkix.AttributeTypeAndValue{ {Type: oidEmailAddress, Value: "test@example.com"}, }, }, } // 4. 构造待签名的TBSCertRequest tbs := tbsCertRequest{ Version: 0, // CSR v1版本对应0 Subject: subj, SubjectPKInfo: spki, Attributes: attributes, } tbsBytes, err := asn1.Marshal(tbs) if err != nil { fmt.Printf("Marshal TBS error: %v\n", err) return } // 5. 调用远程API获取签名 signature, err := remoteSign(tbsBytes) if err != nil { fmt.Printf("Remote sign error: %v\n", err) return } // 6. 构造完整CSR结构 sigAlg := pkix.AlgorithmIdentifier{ Algorithm: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}, // SHA256WithRSA Parameters: asn1.RawValue{Tag: asn1.TagNull, Bytes: []byte{}}, } csr := certificateRequest{ TBSCertRequest: tbs, SignatureAlgorithm: sigAlg, Signature: asn1.BitString{ Bytes: signature, BitLength: len(signature) * 8, }, } // 7. 序列化CSR并编码为PEM csrBytes, err := asn1.Marshal(csr) if err != nil { fmt.Printf("Marshal CSR error: %v\n", err) return } pem.Encode(os.Stdout, &pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: csrBytes, }) }
关键注意事项
- 签名算法一致性:确保远程签名使用的算法与CSR中指定的
SignatureAlgorithm一致(示例中为SHA256WithRSA,OID为1.2.840.113549.1.1.11)。 - TBS数据处理:远程签名时需严格按照算法要求处理TBS数据,例如SHA256WithRSA需先计算TBS数据的SHA256哈希,再用RSA私钥进行PKCS#1 v1.5签名。
- 属性构造:若不需要扩展属性(如邮箱),可将
Attributes字段设为nil,ASN.1序列化时会自动忽略该可选字段。
内容的提问来源于stack exchange,提问作者Eatay Mizrachi
相关产品推荐
相关产品推荐

