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-----
Reference: RFC 5280 https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13
Conforming CAs SHOULD NOT use nameRelativeToCRLIssuer
to specify distribution point names.
The DistributionPointName
MUST NOT use the nameRelativeToCRLIssuer
alternative when cRLIssuer
contains more than one distinguished name.
My feeling is that your crl has : DistributionPointName ::= nameRelativeToCRLIssuer: "Name"
ASN.1 encoding needs
SEQUENCE {
type = OID (e.g. commonName)
value = UTF8String (e.g. "CRL")
}
If using OpenSSL config use fullName
instead of nameRelativeToCRLIssuer