使用Golang解析CRL中的IDP扩展信息

使用Golang解析CRL中的IDP扩展信息 开发者,我用Go编写了一些代码来解析CRL中的IDP扩展信息,但在解析DistributionPoint时遇到了错误。我已经尝试了多次,但无法让它正常工作。你能帮我找出这段代码的问题所在吗?

package main

import (
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"encoding/hex"
	"flag"
	"fmt"
	"os"
	"strings"
)

type IssuingDistributionPoint struct {
	DistributionPoint          asn1.RawValue  `asn1:"optional,tag:0,explicit"`
	OnlyContainsUserCerts      bool           `asn1:"optional,tag:1"`
	OnlyContainsCACerts        bool           `asn1:"optional,tag:2"`
	OnlySomeReasons            asn1.BitString `asn1:"optional,tag:3"`
	IndirectCRL                bool           `asn1:"optional,tag:4"`
	OnlyContainsAttributeCerts bool           `asn1:"optional,tag:5"`
}

var oidMap = map[string]string{
	"2.5.4.3":  "CN",
	"2.5.4.6":  "C",
	"2.5.4.10": "O",
	"2.5.4.11": "OU",
	"1.2.840.113549.1.9.1":       "E",
	"0.9.2342.19200300.100.1.25": "DC",
	"0.9.2342.19200300.100.1.1":  "UID",
}

func parseRDN(data []byte) (string, error) {
	var builder strings.Builder
	var raw asn1.RawValue
	if _, err := asn1.Unmarshal(data, &raw); err != nil {
		return "", fmt.Errorf("initial parsing failed: %v", err)
	}
	if raw.Tag == asn1.TagSequence {
		type rdnSequence []pkix.AttributeTypeAndValue
		var rdns rdnSequence
		if _, err := asn1.Unmarshal(raw.FullBytes, &rdns); err != nil {
			return "", fmt.Errorf("failed to parse rdnSequence: %v", err)
		}

		for i, rdn := range rdns {
			builder.WriteString(fmt.Sprintf("RDN[%d]:\n", i+1))
			oid := rdn.Type.String()
			name := oidMap[oid]
			if name == "" {
				name = oid
			}

			value, err := decodeAttributeValue(rdn.Value)
			if err != nil {
				return "", fmt.Errorf("failed to decode attribute value: %v", err)
			}

			if name == "DC" {
				if decoded, err := decodeDomainComponent(value); err == nil {
					value = decoded
				}
			}

			builder.WriteString(fmt.Sprintf("  Type:%-8s Value:%s\n", name, value))
		}
		return builder.String(), nil
	}

	return "", fmt.Errorf("unexpected tag type: %d", raw.Tag)
}

func decodeAttributeValue(raw interface{}) (string, error) {
	switch v := raw.(type) {
	case string:
		return v, nil
	case []byte:
		if isPrintable(string(v)) {
			return string(v), nil
		}
		return fmt.Sprintf("#%X", v), nil
	default:
		return fmt.Sprintf("%v", v), nil
	}
}

func decodeDomainComponent(value string) (string, error) {
	if strings.HasPrefix(value, "#") {
		decoded, err := hex.DecodeString(value[1:])
		if err != nil {
			return "", fmt.Errorf("HEX decoding failed: %v", err)
		}
		return string(decoded), nil
	}
	return value, nil
}

func isPrintable(s string) bool {
	for _, r := range s {
		if r < 32 || r > 126 {
			return false
		}
	}
	return true
}

func parseGeneralName(gn asn1.RawValue) (interface{}, error) {
	if gn.Class == asn1.ClassContextSpecific && gn.Tag == 1 {
		return parseRDN(gn.FullBytes)
	}

	if gn.Tag == asn1.TagSequence {
		var names []asn1.RawValue
		if _, err := asn1.Unmarshal(gn.Bytes, &names); err != nil {
			return nil, fmt.Errorf("failed to decode GeneralNames: %v", err)
		}

		var results []string
		for _, name := range names {
			res, err := parseGeneralName(name)
			if err != nil {
				return nil, err
			}
			results = append(results, fmt.Sprintf("%v", res))
		}
		return strings.Join(results, ", "), nil
	}

	if gn.Class == asn1.ClassContextSpecific {
		switch gn.Tag {
		case 0, 2, 6: 
			return string(gn.Bytes), nil
		}
	}

	return nil, fmt.Errorf("unsupported GeneralName type: tag=%d class=%d", gn.Tag, gn.Class)
}

func main() {
	crlFilePath := flag.String("crl", "", "Path to the CRL file")
	flag.Parse()

	if *crlFilePath == "" {
		fmt.Println("CRL file path must be provided")
		os.Exit(1)
	}

	derBytes, err := os.ReadFile(*crlFilePath)
	if err != nil {
		fmt.Printf("Failed to read file: %v\n", err)
		os.Exit(1)
	}

	crl, err := x509.ParseRevocationList(derBytes)
	if err != nil {
		fmt.Printf("Failed to parse CRL: %v\n", err)
		os.Exit(1)
	}

	oidIssuingDistributionPoint := asn1.ObjectIdentifier{2, 5, 29, 28}

	for _, ext := range crl.Extensions {
		if ext.Id.Equal(oidIssuingDistributionPoint) {
			var idp IssuingDistributionPoint
			if _, err := asn1.Unmarshal(ext.Value, &idp); err != nil {
				fmt.Printf("Failed to decode IDP extension: %v\n", err)
				continue
			}

			fmt.Printf("IDP Extension Flags:\n")
			fmt.Printf(" Only Contains User Certs: %t\n", idp.OnlyContainsUserCerts)
			fmt.Printf(" Only Contains CA Certs: %t\n", idp.OnlyContainsCACerts)
			fmt.Printf(" Indirect CRL: %t\n", idp.IndirectCRL)

			if len(idp.DistributionPoint.Bytes) > 0 {
				var dpName asn1.RawValue
				if _, err := asn1.Unmarshal(idp.DistributionPoint.Bytes, &dpName); err != nil {
					fmt.Printf("Failed to unpack DistributionPointName: %v\n", err)
					continue
				}

				if dpName.Class == asn1.ClassContextSpecific {
					switch dpName.Tag {
					case 0: // fullName
						fmt.Println("Distribution Point Type: fullName")
						var generalNames []asn1.RawValue
						if _, err := asn1.Unmarshal(dpName.Bytes, &generalNames); err != nil {
							fmt.Printf("Failed to parse GeneralNames: %v\n", err)
							continue
						}

						for i, gn := range generalNames {
							result, err := parseGeneralName(gn)
							if err != nil {
								fmt.Printf("[Entry %d] Parsing error: %v\n", i+1, err)
								continue
							}
							fmt.Printf("[Entry %d] Distribution Point: %s\n", i+1, result)
						}

					case 1: // nameRelativeToCRLIssuer
						fmt.Println("Distribution Point Type: nameRelativeToCRLIssuer")
						result, err := parseRDN(dpName.Bytes)
						if err != nil {
							fmt.Printf("Failed to parse RDN: %v\n", err)
							continue
						}
						fmt.Println(result)

					default:
						fmt.Printf("Unknown distribution point tag: %d\n", dpName.Tag)
					}
				}
			}
		}
	}
}

错误:

IDP Extension Flags:
 Only Contains User Certs: false
 Only Contains CA Certs: false
 Indirect CRL: false
Distribution Point Type: nameRelativeToCRLIssuer
Failed to parse RDN: failed to parse rdnSequence: asn1: structure error: sequence tag mismatch

Pem:

-----BEGIN X509 CRL-----
MIICnTCCAYUCAQEwDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCVVMxCzAJBgNV
BAgMAlVTMQswCQYDVQQHDAJVUzELMAkGA1UECgwCVVMxCzAJBgNVBAMMAlVTMQsw
CQYDVQQLDAJVUxcNMjUwMTAxMDAwMDAwWhcNMjUxMjAxMDAwMDAwWjA1MDMCFByA
Ai74HyQF7pamEty2H+CscB5eFw0yNTAzMjcwMjUxMTBaMAwwCgYDVR0VBAMKAQag
gcswgcgwgasGA1UdHAEB/wSBoDCBnaCBmqGBlzAJBgNVBAYTAkNOMAkGA1UEChMC
Q0EwCgYDVQQDEwNDUkwwEQYKCZImiZPyLGQBGQwDY29tMBQGA1UECxMNSVQgRGVw
YXJ0bWVudDAUBgoJkiaJk/IsZAEBDAZib2IxMjMwFQYKCZImiZPyLGQBGQwHZXhh
bXBsZTAdBgkqhkiG9w0BCQEWEHVzZXJAZXhhbXBsZS5jb20wGAYDVR0UBBECDxnP
/97adO3y9qRGDM7hQDANBgkqhkiG9w0BAQsFAAOCAQEApPcq43Py08J9wMWTXIQT
Q3E30ACBzEW2E+3HZ5818Z/FK7+YYV4umPZ5JQVINqYoTbpRoBrdh8VJyJ2U/B1u
9NDgtMRv7gVHad+uy3ciRG+nvOa9JP4a0a3GMN5nhWIykghH9LBYOL48WCP6r3pO
t8inbw7bSB25HSDIuHHeuChchDOgv926MFmYTphaFY7h6sFRDjHVSSFJEicSRx/t
0OJ8mtfwBqWLw9725u4A5b08FGAfSV0UBE25QqqpE/W5Vt4tDmDfR8idEsQyRbCf
qETKAFd0a7J5MiI4MNj3CaUWUbsq1kZsFfSRFqPJyoiXlUm8jz6n/8eLrV3rDLiS
bg==
-----END X509 CRL-----

更多关于使用Golang解析CRL中的IDP扩展信息的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

你好! 本页面包含我的代码、错误信息以及我正在使用的CRL的PEM格式信息。

更多关于使用Golang解析CRL中的IDP扩展信息的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好! 当然可以!请分享你的Go代码,以及你遇到的特定错误信息。如果可能的话,也请附上你正在使用的任何相关CRL示例数据,以及你如何处理IDP扩展的详细信息。这样,我就能分析问题并有效地帮助排查。

你的代码在解析nameRelativeToCRLIssuer类型的DistributionPoint时遇到了ASN.1序列标签不匹配的问题。问题在于parseRDN函数期望接收一个完整的RDN序列,但实际传入的是经过ASN.1编码的RelativeDistinguishedName结构。

以下是修复后的parseRDN函数和相关的解析逻辑:

func parseRDN(data []byte) (string, error) {
	var builder strings.Builder
	
	// 直接解析为pkix.AttributeTypeAndValue序列
	var atvs []pkix.AttributeTypeAndValue
	if _, err := asn1.Unmarshal(data, &atvs); err != nil {
		return "", fmt.Errorf("failed to parse RDN sequence: %v", err)
	}

	for i, atv := range atvs {
		builder.WriteString(fmt.Sprintf("RDN[%d]:\n", i+1))
		oid := atv.Type.String()
		name := oidMap[oid]
		if name == "" {
			name = oid
		}

		value, err := decodeAttributeValue(atv.Value)
		if err != nil {
			return "", fmt.Errorf("failed to decode attribute value: %v", err)
		}

		if name == "DC" {
			if decoded, err := decodeDomainComponent(value); err == nil {
				value = decoded
			}
		}

		builder.WriteString(fmt.Sprintf("  Type:%-8s Value:%s\n", name, value))
	}
	return builder.String(), nil
}

// 修改main函数中解析nameRelativeToCRLIssuer的部分
if dpName.Tag == 1 { // nameRelativeToCRLIssuer
	fmt.Println("Distribution Point Type: nameRelativeToCRLIssuer")
	
	// nameRelativeToCRLIssuer是RelativeDistinguishedName类型
	// 需要直接解析为RDN序列
	result, err := parseRDN(dpName.Bytes)
	if err != nil {
		fmt.Printf("Failed to parse RDN: %v\n", err)
		continue
	}
	fmt.Println(result)
}

另外,你的parseGeneralName函数在处理directoryName(tag 4)时也有问题。需要添加对directoryName的支持:

func parseGeneralName(gn asn1.RawValue) (interface{}, error) {
	if gn.Class == asn1.ClassContextSpecific {
		switch gn.Tag {
		case 0: // otherName
			return fmt.Sprintf("otherName: %X", gn.Bytes), nil
		case 1: // rfc822Name
			return fmt.Sprintf("rfc822Name: %s", string(gn.Bytes)), nil
		case 2: // dNSName
			return fmt.Sprintf("dNSName: %s", string(gn.Bytes)), nil
		case 4: // directoryName
			// directoryName是Name类型,需要特殊处理
			return parseRDN(gn.Bytes)
		case 6: // uniformResourceIdentifier
			return fmt.Sprintf("URI: %s", string(gn.Bytes)), nil
		case 7: // iPAddress
			return fmt.Sprintf("IP: %X", gn.Bytes), nil
		case 8: // registeredID
			var oid asn1.ObjectIdentifier
			if _, err := asn1.Unmarshal(gn.Bytes, &oid); err != nil {
				return nil, fmt.Errorf("failed to parse registeredID: %v", err)
			}
			return fmt.Sprintf("registeredID: %s", oid.String()), nil
		}
	}

	return nil, fmt.Errorf("unsupported GeneralName type: tag=%d class=%d", gn.Tag, gn.Class)
}

对于你提供的CRL数据,IDP扩展可能不包含DistributionPoint字段。你可以添加一个检查来避免空指针错误:

if idp.DistributionPoint.FullBytes != nil {
	// 现有的解析逻辑
}

完整的修复示例:

package main

import (
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"encoding/hex"
	"flag"
	"fmt"
	"os"
	"strings"
)

type IssuingDistributionPoint struct {
	DistributionPoint          asn1.RawValue  `asn1:"optional,tag:0"`
	OnlyContainsUserCerts      bool           `asn1:"optional,tag:1"`
	OnlyContainsCACerts        bool           `asn1:"optional,tag:2"`
	OnlySomeReasons            asn1.BitString `asn1:"optional,tag:3"`
	IndirectCRL                bool           `asn1:"optional,tag:4"`
	OnlyContainsAttributeCerts bool           `asn1:"optional,tag:5"`
}

var oidMap = map[string]string{
	"2.5.4.3":  "CN",
	"2.5.4.6":  "C",
	"2.5.4.10": "O",
	"2.5.4.11": "OU",
	"1.2.840.113549.1.9.1":       "E",
	"0.9.2342.19200300.100.1.25": "DC",
	"0.9.2342.19200300.100.1.1":  "UID",
}

func parseRDN(data []byte) (string, error) {
	var builder strings.Builder
	
	var atvs []pkix.AttributeTypeAndValue
	if _, err := asn1.Unmarshal(data, &atvs); err != nil {
		return "", fmt.Errorf("failed to parse RDN sequence: %v", err)
	}

	for i, atv := range atvs {
		builder.WriteString(fmt.Sprintf("RDN[%d]:\n", i+1))
		oid := atv.Type.String()
		name := oidMap[oid]
		if name == "" {
			name = oid
		}

		value, err := decodeAttributeValue(atv.Value)
		if err != nil {
			return "", fmt.Errorf("failed to decode attribute value: %v", err)
		}

		if name == "DC" {
			if decoded, err := decodeDomainComponent(value); err == nil {
				value = decoded
			}
		}

		builder.WriteString(fmt.Sprintf("  Type:%-8s Value:%s\n", name, value))
	}
	return builder.String(), nil
}

func decodeAttributeValue(raw interface{}) (string, error) {
	switch v := raw.(type) {
	case string:
		return v, nil
	case []byte:
		if isPrintable(string(v)) {
			return string(v), nil
		}
		return fmt.Sprintf("#%X", v), nil
	default:
		return fmt.Sprintf("%v", v), nil
	}
}

func decodeDomainComponent(value string) (string, error) {
	if strings.HasPrefix(value, "#") {
		decoded, err := hex.DecodeString(value[1:])
		if err != nil {
			return "", fmt.Errorf("HEX decoding failed: %v", err)
		}
		return string(decoded), nil
	}
	return value, nil
}

func isPrintable(s string) bool {
	for _, r := range s {
		if r < 32 || r > 126 {
			return false
		}
	}
	return true
}

func parseGeneralName(gn asn1.RawValue) (interface{}, error) {
	if gn.Class == asn1.ClassContextSpecific {
		switch gn.Tag {
		case 0:
			return fmt.Sprintf("otherName: %X", gn.Bytes), nil
		case 1:
			return fmt.Sprintf("rfc822Name: %s", string(gn.Bytes)), nil
		case 2:
			return fmt.Sprintf("dNSName: %s", string(gn.Bytes)), nil
		case 4:
			return parseRDN(gn.Bytes)
		case 6:
			return fmt.Sprintf("URI: %s", string(gn.Bytes)), nil
		case 7:
			return fmt.Sprintf("IP: %X", gn.Bytes), nil
		case 8:
			var oid asn1.ObjectIdentifier
			if _, err := asn1.Unmarshal(gn.Bytes, &oid); err != nil {
				return nil, fmt.Errorf("failed to parse registeredID: %v", err)
			}
			return fmt.Sprintf("registeredID: %s", oid.String()), nil
		}
	}

	return nil, fmt.Errorf("unsupported GeneralName type: tag=%d class=%d", gn.Tag, gn.Class)
}

func main() {
	crlFilePath := flag.String("crl", "", "Path to the CRL file")
	flag.Parse()

	if *crlFilePath == "" {
		fmt.Println("CRL file path must be provided")
		os.Exit(1)
	}

	derBytes, err := os.ReadFile(*crlFilePath)
	if err != nil {
		fmt.Printf("Failed to read file: %v\n", err)
		os.Exit(1)
	}

	crl, err := x509.ParseRevocationList(derBytes)
	if err != nil {
		fmt.Printf("Failed to parse CRL: %v\n", err)
		os.Exit(1)
	}

	oidIssuingDistributionPoint := asn1.ObjectIdentifier{2, 5, 29, 28}

	for _, ext := range crl.Extensions {
		if ext.Id.Equal(oidIssuingDistributionPoint) {
			var idp IssuingDistributionPoint
			if _, err := asn1.Unmarshal(ext.Value, &idp); err != nil {
				fmt.Printf("Failed to decode IDP extension: %v\n", err)
				continue
			}

			fmt.Printf("IDP Extension Flags:\n")
			fmt.Printf(" Only Contains User Certs: %t\n", idp.OnlyContainsUserCerts)
			fmt.Printf(" Only Contains CA Certs: %t\n", idp.OnlyContainsCACerts)
			fmt.Printf(" Indirect CRL: %t\n", idp.IndirectCRL)

			if len(idp.DistributionPoint.Bytes) > 0 {
				var dpName asn1.RawValue
				if _, err := asn1.Unmarshal(idp.DistributionPoint.Bytes, &dpName); err != nil {
					fmt.Printf("Failed to unpack DistributionPointName: %v\n", err)
					continue
				}

				if dpName.Class == asn1.ClassContextSpecific {
					switch dpName.Tag {
					case 0:
						fmt.Println("Distribution Point Type: fullName")
						var generalNames []asn1.RawValue
						if _, err := asn1.Unmarshal(dpName.Bytes, &generalNames); err != nil {
							fmt.Printf("Failed to parse GeneralNames: %v\n", err)
							continue
						}

						for i, gn := range generalNames {
							result, err := parseGeneralName(gn)
							if err != nil {
								fmt.Printf("[Entry %d] Parsing error: %v\n", i+1, err)
								continue
							}
							fmt.Printf("[Entry %d] Distribution Point: %s\n", i+1, result)
						}

					case 1:
						fmt.Println("Distribution Point Type: nameRelativeToCRLIssuer")
						result, err := parseRDN(dpName.Bytes)
						if err != nil {
							fmt.Printf("Failed to parse RDN: %v\n", err)
							continue
						}
						fmt.Println(result)

					default:
						fmt.Printf("Unknown distribution point tag: %d\n", dpName.Tag)
					}
				}
			}
		}
	}
}

主要修改:

  1. 简化了parseRDN函数,直接解析为pkix.AttributeTypeAndValue序列
  2. 修复了parseGeneralName函数对directoryName(tag 4)的支持
  3. 移除了IssuingDistributionPoint结构体标签中的explicit标记,因为IDP扩展使用隐式标签
回到顶部