How to parse IDP extension information in a CRL using Go

Developer, I wrote some code in Go to parse the IDP extension information in a CRL, but I’m encountering an error while parsing the DistributionPoint. I’ve tried multiple times, but I can’t get it to work. Can you help me figure out what is wrong with this code?

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)
					}
				}
			}
		}
	}
}

error:

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-----

Hello!
Absolutely! Please share your Go code, along with the specific error message you’re encountering. If possible, also include any relevant CRL sample data you’re using and details on how you’re handling the IDP extension. That way, I can analyze the issue and help troubleshoot it effectively.

Hello!
This page contains my code, error messages, and the PEM format information of the CRL I am using.