crypto/x509/pkix - PKIX 类型和结构
概述
crypto/x509/pkix 包提供了 X.509 证书中使用的 PKIX(Public Key Infrastructure using X.509)相关类型。
主要用途:
- 📛 证书主题和颁发者:
Name结构体 - 📋 证书属性:
AttributeTypeAndValue - 🔗 RDN 序列:
RDNSequence - 📜 CRL(证书吊销列表):
CertificateList - ➕ 扩展:
Extension
与 crypto/x509 的关系:
crypto/x509:证书和 CRL 的解析和创建crypto/x509/pkix:底层的 PKIX 类型定义- 通常配合使用,
pkix提供类型,x509提供操作
核心类型
1. Name - 可分辨名称(DN)
type Name struct {
Country []string
Organization []string
OrganizationalUnit []string
Locality []string
Province []string
StreetAddress []string
PostalCode []string
SerialNumber string
CommonName string
// 额外名称
ExtraNames []AttributeTypeAndValue
}
字段说明:
标准字段(OID 映射)
Country:国家(C)- OID: 2.5.4.6Organization:组织(O)- OID: 2.5.4.10OrganizationalUnit:组织单位(OU)- OID: 2.5.4.11Locality:地区/城市(L)- OID: 2.5.4.7Province:省份/州(ST)- OID: 2.5.4.8StreetAddress:街道地址 - OID: 2.5.4.9PostalCode:邮政编码 - OID: 2.5.4.17SerialNumber:序列号 - OID: 2.5.4.5CommonName:通用名称(CN)- OID: 2.5.4.3
额外字段
ExtraNames:自定义 OID 的名称属性
字符串表示:
CN=example.com,O=Example Inc,C=US
2. AttributeTypeAndValue - 属性类型和值
type AttributeTypeAndValue struct {
Type asn1.ObjectIdentifier // OID
Value interface{} // 值
}
用途:表示一个名称属性对。
常见 OID:
// 标准 OID
oidCountry = []int{2, 5, 4, 6}
oidOrganization = []int{2, 5, 4, 10}
oidOrganizationalUnit = []int{2, 5, 4, 11}
oidCommonName = []int{2, 5, 4, 3}
oidEmailAddress = []int{1, 2, 840, 113549, 1, 9, 1}
3. RDNSequence - 相对可分辨名称序列
type RDNSequence [][]AttributeTypeAndValue
功能:表示 X.500 可分辨名称的 ASN.1 编码形式。
结构说明:
- 外层
[]:RDN(Relative Distinguished Name)序列 - 内层
[]:每个 RDN 中的多个属性 - 通常每个 RDN 只有一个属性
示例:
CN=example.com, O=Example Inc, C=US
编码为:
[
[{Type: OID_CN, Value: "example.com"}],
[{Type: OID_O, Value: "Example Inc"}],
[{Type: OID_C, Value: "US"}]
]
4. Extension - 证书扩展
type Extension struct {
Id asn1.ObjectIdentifier // 扩展 OID
Critical bool // 是否关键扩展
Value []byte // 扩展值(ASN.1 编码)
}
常见扩展 OID:
// 密钥用途
oidExtensionKeyUsage = []int{2, 5, 29, 15}
// 扩展密钥用途
oidExtensionExtKeyUsage = []int{2, 5, 29, 37}
// 主题备用名称
oidExtensionSubjectAltName = []int{2, 5, 29, 17}
// 颁发者备用名称
oidExtensionIssuerAltName = []int{2, 5, 29, 18}
// 基本约束(CA 证书)
oidExtensionBasicConstraints = []int{2, 5, 29, 19}
// 名称约束
oidExtensionNameConstraints = []int{2, 5, 29, 30}
// CRL 分发点
oidExtensionCRLDistributionPoints = []int{2, 5, 29, 31}
// 认证机构信息访问
oidExtensionAuthorityInfoAccess = []int{1, 3, 6, 1, 5, 5, 7, 1, 1}
// 主题密钥标识符
oidExtensionSubjectKeyId = []int{2, 5, 29, 14}
// 授权密钥标识符
oidExtensionAuthorityKeyId = []int{2, 5, 29, 35}
// 证书策略
oidExtensionCertificatePolicies = []int{2, 5, 29, 32}
// 策略约束
oidExtensionPolicyConstraints = []int{2, 5, 29, 36}
// 抑制策略映射
oidExtensionInhibitAnyPolicy = []int{2, 5, 29, 54}
// 主题目录属性
oidExtensionSubjectDirectoryAttributes = []int{2, 5, 29, 9}
5. CertificateList - 证书吊销列表(CRL)
type CertificateList struct {
TBSCertList TBSCertList
SignatureAlgorithm pkix.AlgorithmIdentifier
SignatureValue asn1.BitString
}
功能:表示证书吊销列表。
6. TBSCertList - 待签名 CRL
type TBSCertList struct {
Version int
Signature AlgorithmIdentifier
Issuer Name
ThisUpdate time.Time
NextUpdate time.Time
RevokedCertificates []RevokedCertificate
Extensions []Extension
}
7. RevokedCertificate - 吊销的证书
type RevokedCertificate struct {
SerialNumber *big.Int
RevocationTime time.Time
Extensions []Extension
}
8. AlgorithmIdentifier - 算法标识符
type AlgorithmIdentifier struct {
Algorithm asn1.ObjectIdentifier
Parameters asn1.RawValue
}
Name 类型详解
示例 1:创建证书主题
package main
import (
"crypto/x509/pkix"
"fmt"
)
func main() {
// 1. 创建完整的主题名称
name := pkix.Name{
Country: []string{"CN"},
Province: []string{"Beijing"},
Locality: []string{"Beijing"},
Organization: []string{"Example Inc"},
OrganizationalUnit: []string{"IT Department"},
StreetAddress: []string{"Zhongguancun Street"},
PostalCode: []string{"100080"},
SerialNumber: "12345",
CommonName: "example.com",
}
// 2. 转换为字符串
fmt.Printf("主题字符串:%s\n", name.String())
// 3. 访问字段
fmt.Printf("国家:%s\n", name.Country)
fmt.Printf("组织:%s\n", name.Organization)
fmt.Printf("通用名称:%s\n", name.CommonName)
// 4. 添加自定义属性
name.ExtraNames = []pkix.AttributeTypeAndValue{
{
Type: []int{1, 2, 840, 113549, 1, 9, 1}, // Email OID
Value: "admin@example.com",
},
}
}
输出:
主题字符串:CN=example.com,O=Example Inc,OU=IT Department,L=Beijing,ST=Beijing,C=CN
国家:[CN]
组织:[Example Inc]
通用名称:example.com
示例 2:从字符串解析名称
package main
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"log"
"strings"
)
// ParseDN 从字符串解析可分辨名称
// 格式:CN=example.com,O=Example Inc,C=US
func ParseDN(dn string) (pkix.Name, error) {
name := pkix.Name{}
parts := strings.Split(dn, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
kv := strings.SplitN(part, "=", 2)
if len(kv) != 2 {
continue
}
key := strings.TrimSpace(kv[0])
value := strings.TrimSpace(kv[1])
switch key {
case "C":
name.Country = append(name.Country, value)
case "O":
name.Organization = append(name.Organization, value)
case "OU":
name.OrganizationalUnit = append(name.OrganizationalUnit, value)
case "L":
name.Locality = append(name.Locality, value)
case "ST", "S":
name.Province = append(name.Province, value)
case "STREET":
name.StreetAddress = append(name.StreetAddress, value)
case "POSTALCODE":
name.PostalCode = append(name.PostalCode, value)
case "SN":
name.SerialNumber = value
case "CN":
name.CommonName = value
}
}
return name, nil
}
func main() {
dn := "CN=example.com,O=Example Inc,OU=IT Department,L=Beijing,ST=Beijing,C=CN"
name, err := ParseDN(dn)
if err != nil {
log.Fatal(err)
}
fmt.Printf("解析结果:%+v\n", name)
fmt.Printf("通用名称:%s\n", name.CommonName)
fmt.Printf("组织:%s\n", name.Organization[0])
}
示例 3:使用 ExtraNames 添加自定义属性
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"log"
"math/big"
"os"
"time"
)
func main() {
// 1. 生成密钥
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
// 2. 创建主题名称(包含自定义属性)
name := pkix.Name{
CommonName: "example.com",
Organization: []string{"Example Inc"},
Country: []string{"US"},
// 添加自定义属性
ExtraNames: []pkix.AttributeTypeAndValue{
{
// OID: 1.2.840.113549.1.9.1 (Email)
Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1},
Value: "admin@example.com",
},
{
// OID: 0.9.2342.19200300.100.1.1 (UserID)
Type: asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 1},
Value: "user123",
},
},
}
// 3. 创建证书模板
serialNumber, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
template := &x509.Certificate{
SerialNumber: serialNumber,
Subject: name,
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
// 4. 创建证书
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
if err != nil {
log.Fatal(err)
}
// 5. 保存证书
certFile, err := os.Create("custom-dn.crt")
if err != nil {
log.Fatal(err)
}
defer certFile.Close()
pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
fmt.Println("✓ 包含自定义属性的证书生成成功")
fmt.Println(" 证书:custom-dn.crt")
}
Extension 类型详解
示例 4:创建自定义扩展
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"log"
"math/big"
"os"
"time"
)
func main() {
// 1. 生成密钥
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
// 2. 创建证书模板
serialNumber, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
template := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "example.com",
Organization: []string{"Example Inc"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
// 3. 添加自定义扩展
customOID := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 99999, 1}
customValue := []byte("Custom Extension Value")
template.ExtraExtensions = []pkix.Extension{
{
Id: customOID,
Critical: false, // 非关键扩展
Value: customValue,
},
{
// 添加自定义策略 OID
Id: asn1.ObjectIdentifier{2, 5, 29, 32}, // Certificate Policies
Critical: false,
Value: []byte{0x30, 0x0f, 0x30, 0x0d, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xdc, 0x01, 0x01},
},
}
// 4. 创建证书
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
if err != nil {
log.Fatal(err)
}
// 5. 保存证书
certFile, err := os.Create("custom-ext.crt")
if err != nil {
log.Fatal(err)
}
defer certFile.Close()
pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
fmt.Println("✓ 包含自定义扩展的证书生成成功")
}
示例 5:解析证书扩展
package main
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"encoding/asn1"
"fmt"
"io/ioutil"
"log"
)
func main() {
// 1. 读取证书
certPEM, err := ioutil.ReadFile("certificate.crt")
if err != nil {
log.Fatal(err)
}
block, _ := pem.Decode(certPEM)
if block == nil {
log.Fatal("无法解析证书")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
log.Fatal(err)
}
// 2. 显示标准扩展
fmt.Printf("密钥用途:%v\n", cert.KeyUsage)
fmt.Printf("扩展密钥用途:%v\n", cert.ExtKeyUsage)
fmt.Printf("DNS 名称:%v\n", cert.DNSNames)
fmt.Printf("IP 地址:%v\n", cert.IPAddresses)
fmt.Printf("CRL 分发点:%v\n", cert.CRLDistributionPoints)
fmt.Printf("OCSP 服务器:%v\n", cert.OCSPServer)
// 3. 显示所有扩展(包括未知扩展)
fmt.Println("\n所有扩展:")
for _, ext := range cert.Extensions {
fmt.Printf("\n扩展 OID: %v\n", ext.Id)
fmt.Printf(" 关键:%v\n", ext.Critical)
fmt.Printf(" 值长度:%d 字节\n", len(ext.Value))
// 尝试解析扩展值
var rawValue asn1.RawValue
rest, err := asn1.Unmarshal(ext.Value, &rawValue)
if err != nil {
fmt.Printf(" 解析失败:%v\n", err)
} else {
fmt.Printf(" 剩余字节:%d\n", len(rest))
fmt.Printf(" 标签:%d\n", rawValue.Tag)
fmt.Printf(" 类:%d\n", rawValue.Class)
}
}
}
CRL(证书吊销列表)
示例 6:创建 CRL
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"log"
"math/big"
"os"
"time"
)
func main() {
// 1. 生成 CA 密钥
caPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
// 2. 创建 CA 证书
caTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "Test CA",
Organization: []string{"Test"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
IsCA: true,
}
caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caPriv.PublicKey, caPriv)
if err != nil {
log.Fatal(err)
}
caCert, _ := x509.ParseCertificate(caDER)
// 3. 创建 CRL
serialNumber, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 64))
crlTemplate := &x509.RevocationList{
Number: serialNumber,
Issuer: pkix.Name{
CommonName: "Test CA",
Organization: []string{"Test"},
},
ThisUpdate: time.Now(),
NextUpdate: time.Now().Add(30 * 24 * time.Hour), // 30 天
// 吊销的证书
RevokedCertificateEntries: []x509.RevocationListEntry{
{
SerialNumber: big.NewInt(1001),
RevocationTime: time.Now().Add(-24 * time.Hour),
ReasonCode: x509.KeyCompromise, // 密钥泄露
},
{
SerialNumber: big.NewInt(1002),
RevocationTime: time.Now().Add(-12 * time.Hour),
ReasonCode: x509.CACompromise, // CA 密钥泄露
},
},
}
// 4. 签名 CRL
crlDER, err := x509.CreateRevocationList(rand.Reader, crlTemplate, caCert, caPriv)
if err != nil {
log.Fatal(err)
}
// 5. 保存 CRL
crlFile, err := os.Create("ca.crl")
if err != nil {
log.Fatal(err)
}
defer crlFile.Close()
pem.Encode(crlFile, &pem.Block{Type: "X509 CRL", Bytes: crlDER})
fmt.Println("✓ CRL 生成成功")
fmt.Println(" CRL 文件:ca.crl")
fmt.Printf(" 吊销证书数量:%d\n", len(crlTemplate.RevokedCertificateEntries))
}
示例 7:解析 CRL
package main
import (
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
)
func main() {
// 1. 读取 CRL 文件
crlPEM, err := ioutil.ReadFile("ca.crl")
if err != nil {
log.Fatal(err)
}
block, _ := pem.Decode(crlPEM)
if block == nil {
log.Fatal("无法解析 CRL PEM")
}
// 2. 解析 CRL
crl, err := x509.ParseRevocationList(block.Bytes)
if err != nil {
log.Fatal(err)
}
// 3. 显示 CRL 信息
fmt.Println("CRL 信息:")
fmt.Printf(" 颁发者:%s\n", crl.Issuer.String())
fmt.Printf(" 序列号:%d\n", crl.Number)
fmt.Printf(" 本次更新:%s\n", crl.ThisUpdate)
fmt.Printf(" 下次更新:%s\n", crl.NextUpdate)
fmt.Printf(" 吊销数量:%d\n", len(crl.RevokedCertificateEntries))
// 4. 显示吊销的证书
fmt.Println("\n吊销的证书:")
for i, entry := range crl.RevokedCertificateEntries {
fmt.Printf(" %d. 序列号:%d\n", i+1, entry.SerialNumber)
fmt.Printf(" 吊销时间:%s\n", entry.RevocationTime)
fmt.Printf(" 原因代码:%v\n", entry.ReasonCode)
}
// 5. 检查特定证书是否被吊销
checkSerial := big.NewInt(1001)
for _, entry := range crl.RevokedCertificateEntries {
if entry.SerialNumber.Cmp(checkSerial) == 0 {
fmt.Printf("\n⚠️ 证书 %s 已被吊销\n", checkSerial)
break
}
}
}
示例 8:使用 pkix.Marshal 和 Unmarshal
package main
import (
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"log"
)
// NameAttributes 名称属性
type NameAttributes struct {
CommonName string `asn1:"utf8"`
Organization string `asn1:"utf8"`
Country string `asn1:"utf8"`
}
func main() {
// 1. 创建名称属性
attrs := NameAttributes{
CommonName: "example.com",
Organization: "Example Inc",
Country: "US",
}
// 2. ASN.1 编码
encoded, err := asn1.Marshal(attrs)
if err != nil {
log.Fatal(err)
}
fmt.Printf("编码后长度:%d 字节\n", len(encoded))
// 3. ASN.1 解码
var decoded NameAttributes
rest, err := asn1.Unmarshal(encoded, &decoded)
if err != nil {
log.Fatal(err)
}
fmt.Printf("解码后:%+v\n", decoded)
fmt.Printf("剩余字节:%d\n", len(rest))
// 4. 创建 RDNSequence
rdnSeq := pkix.RDNSequence{
[]pkix.AttributeTypeAndValue{
{Type: []int{2, 5, 4, 6}, Value: "US"}, // Country
},
[]pkix.AttributeTypeAndValue{
{Type: []int{2, 5, 4, 10}, Value: "Example Inc"}, // Organization
},
[]pkix.AttributeTypeAndValue{
{Type: []int{2, 5, 4, 3}, Value: "example.com"}, // CommonName
},
}
// 5. 编码 RDNSequence
rdnEncoded, err := asn1.Marshal(rdnSeq)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nRDN 编码长度:%d 字节\n", len(rdnEncoded))
// 6. 解码 RDNSequence
var rdnDecoded pkix.RDNSequence
asn1.Unmarshal(rdnEncoded, &rdnDecoded)
fmt.Printf("RDN 解码:\n")
for _, rdn := range rdnDecoded {
for _, attr := range rdn {
fmt.Printf(" %v = %v\n", attr.Type, attr.Value)
}
}
}
实用工具函数
示例 9:名称比较工具
package main
import (
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"sort"
"strings"
)
// CompareNames 比较两个名称是否相等
func CompareNames(a, b pkix.Name) bool {
// 比较标准字段
if !compareStringSlice(a.Country, b.Country) ||
!compareStringSlice(a.Organization, b.Organization) ||
!compareStringSlice(a.OrganizationalUnit, b.OrganizationalUnit) ||
!compareStringSlice(a.Locality, b.Locality) ||
!compareStringSlice(a.Province, b.Province) ||
!compareStringSlice(a.StreetAddress, b.StreetAddress) ||
!compareStringSlice(a.PostalCode, b.PostalCode) ||
a.SerialNumber != b.SerialNumber ||
a.CommonName != b.CommonName {
return false
}
// 比较 ExtraNames
if len(a.ExtraNames) != len(b.ExtraNames) {
return false
}
for i := range a.ExtraNames {
if !compareOID(a.ExtraNames[i].Type, b.ExtraNames[i].Type) ||
a.ExtraNames[i].Value != b.ExtraNames[i].Value {
return false
}
}
return true
}
func compareStringSlice(a, b []string) bool {
if len(a) != len(b) {
return false
}
// 排序后比较
aCopy := make([]string, len(a))
bCopy := make([]string, len(b))
copy(aCopy, a)
copy(bCopy, b)
sort.Strings(aCopy)
sort.Strings(bCopy)
for i := range aCopy {
if aCopy[i] != bCopy[i] {
return false
}
}
return true
}
func compareOID(a, b asn1.ObjectIdentifier) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
// NameToDN 将名称转换为 DN 字符串
func NameToDN(name pkix.Name) string {
var parts []string
if name.CommonName != "" {
parts = append(parts, fmt.Sprintf("CN=%s", name.CommonName))
}
if len(name.Organization) > 0 {
parts = append(parts, fmt.Sprintf("O=%s", strings.Join(name.Organization, ", ")))
}
if len(name.OrganizationalUnit) > 0 {
parts = append(parts, fmt.Sprintf("OU=%s", strings.Join(name.OrganizationalUnit, ", ")))
}
if len(name.Locality) > 0 {
parts = append(parts, fmt.Sprintf("L=%s", strings.Join(name.Locality, ", ")))
}
if len(name.Province) > 0 {
parts = append(parts, fmt.Sprintf("ST=%s", strings.Join(name.Province, ", ")))
}
if len(name.Country) > 0 {
parts = append(parts, fmt.Sprintf("C=%s", strings.Join(name.Country, ", ")))
}
return strings.Join(parts, ", ")
}
func main() {
name1 := pkix.Name{
CommonName: "example.com",
Organization: []string{"Example Inc"},
Country: []string{"US"},
}
name2 := pkix.Name{
CommonName: "example.com",
Organization: []string{"Example Inc"},
Country: []string{"US"},
}
fmt.Printf("名称 1: %s\n", NameToDN(name1))
fmt.Printf("名称 2: %s\n", NameToDN(name2))
fmt.Printf("是否相等:%v\n", CompareNames(name1, name2))
}
安全最佳实践
✅ 推荐做法
-
使用标准 OID
// ✅ 推荐:使用标准 OID name := pkix.Name{ CommonName: "example.com", Organization: []string{"Example Inc"}, Country: []string{"US"}, } -
正确设置扩展
// ✅ 关键扩展必须设置 Critical=true template.ExtraExtensions = []pkix.Extension{ { Id: oidExtensionBasicConstraints, Critical: true, // CA 证书必须标记为关键 Value: value, }, } -
使用有意义的主题
// ✅ 提供完整的主题信息 name := pkix.Name{ Country: []string{"US"}, Organization: []string{"Example Inc"}, OrganizationalUnit: []string{"IT Department"}, CommonName: "example.com", }
❌ 不安全做法
-
不要使用过时的字段
// ⚠️ 避免在 CommonName 中仅使用域名(现代浏览器已不推荐) // 应使用 SAN 扩展 -
不要忽略关键扩展
// ❌ CA 证书的基本约束必须标记为关键
总结
核心类型
// 名称相关
Name // 可分辨名称
AttributeTypeAndValue // 属性类型和值
RDNSequence // RDN 序列
// 扩展
Extension // 证书扩展
// CRL 相关
CertificateList // 证书吊销列表
TBSCertList // 待签名 CRL
RevokedCertificate // 吊销的证书
AlgorithmIdentifier // 算法标识符
使用场景
| 场景 | 推荐类型 | 说明 |
|---|---|---|
| 证书主题 | Name | 设置证书持有者信息 |
| 证书颁发者 | Name | 设置证书颁发者信息 |
| 自定义属性 | AttributeTypeAndValue + ExtraNames | 添加自定义 OID |
| 证书扩展 | Extension | 添加自定义扩展 |
| CRL 创建 | CertificateList | 创建证书吊销列表 |
| ASN.1 编码 | RDNSequence | 名称的 ASN.1 表示 |
常见 OID
| OID | 名称 | 用途 |
|---|---|---|
| 2.5.4.3 | CN | 通用名称 |
| 2.5.4.6 | C | 国家 |
| 2.5.4.7 | L | 地区 |
| 2.5.4.8 | ST | 省份 |
| 2.5.4.10 | O | 组织 |
| 2.5.4.11 | OU | 组织单位 |
| 1.2.840.113549.1.9.1 | 邮箱地址 |
参考资料
最后更新:2026-04-03
Go 版本:Go 1.23+
安全状态:✅ 推荐使用(正确配置下)