Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.6
  • Organization:组织(O)- OID: 2.5.4.10
  • OrganizationalUnit:组织单位(OU)- OID: 2.5.4.11
  • Locality:地区/城市(L)- OID: 2.5.4.7
  • Province:省份/州(ST)- OID: 2.5.4.8
  • StreetAddress:街道地址 - OID: 2.5.4.9
  • PostalCode:邮政编码 - OID: 2.5.4.17
  • SerialNumber:序列号 - OID: 2.5.4.5
  • CommonName:通用名称(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))
}

安全最佳实践

✅ 推荐做法

  1. 使用标准 OID

    // ✅ 推荐:使用标准 OID
    name := pkix.Name{
        CommonName:   "example.com",
        Organization: []string{"Example Inc"},
        Country:      []string{"US"},
    }
    
  2. 正确设置扩展

    // ✅ 关键扩展必须设置 Critical=true
    template.ExtraExtensions = []pkix.Extension{
        {
            Id:       oidExtensionBasicConstraints,
            Critical: true, // CA 证书必须标记为关键
            Value:    value,
        },
    }
    
  3. 使用有意义的主题

    // ✅ 提供完整的主题信息
    name := pkix.Name{
        Country:            []string{"US"},
        Organization:       []string{"Example Inc"},
        OrganizationalUnit: []string{"IT Department"},
        CommonName:         "example.com",
    }
    

❌ 不安全做法

  1. 不要使用过时的字段

    // ⚠️ 避免在 CommonName 中仅使用域名(现代浏览器已不推荐)
    // 应使用 SAN 扩展
    
  2. 不要忽略关键扩展

    // ❌ 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.3CN通用名称
2.5.4.6C国家
2.5.4.7L地区
2.5.4.8ST省份
2.5.4.10O组织
2.5.4.11OU组织单位
1.2.840.113549.1.9.1Email邮箱地址

参考资料


最后更新:2026-04-03
Go 版本:Go 1.23+
安全状态:✅ 推荐使用(正确配置下)