github.com/letsencrypt/boulder@v0.20251208.0/crl/idp/idp.go (about)

     1  package idp
     2  
     3  import (
     4  	"crypto/x509/pkix"
     5  	"encoding/asn1"
     6  	"errors"
     7  	"fmt"
     8  )
     9  
    10  var idpOID = asn1.ObjectIdentifier{2, 5, 29, 28} // id-ce-issuingDistributionPoint
    11  
    12  // issuingDistributionPoint represents the ASN.1 IssuingDistributionPoint
    13  // SEQUENCE as defined in RFC 5280 Section 5.2.5. We only use three of the
    14  // fields, so the others are omitted.
    15  type issuingDistributionPoint struct {
    16  	DistributionPoint     distributionPointName `asn1:"optional,tag:0"`
    17  	OnlyContainsUserCerts bool                  `asn1:"optional,tag:1"`
    18  	OnlyContainsCACerts   bool                  `asn1:"optional,tag:2"`
    19  }
    20  
    21  // distributionPointName represents the ASN.1 DistributionPointName CHOICE as
    22  // defined in RFC 5280 Section 4.2.1.13. We only use one of the fields, so the
    23  // others are omitted.
    24  type distributionPointName struct {
    25  	// Technically, FullName is of type GeneralNames, which is of type SEQUENCE OF
    26  	// GeneralName. But GeneralName itself is of type CHOICE, and the asn1.Marshal
    27  	// function doesn't support marshalling structs to CHOICEs, so we have to use
    28  	// asn1.RawValue and encode the GeneralName ourselves.
    29  	FullName []asn1.RawValue `asn1:"optional,tag:0"`
    30  }
    31  
    32  // MakeUserCertsExt returns a critical IssuingDistributionPoint extension
    33  // containing the given URLs and with the OnlyContainsUserCerts boolean set to
    34  // true.
    35  func MakeUserCertsExt(urls []string) (pkix.Extension, error) {
    36  	var gns []asn1.RawValue
    37  	for _, url := range urls {
    38  		gns = append(gns, asn1.RawValue{ // GeneralName
    39  			Class: 2, // context-specific
    40  			Tag:   6, // uniformResourceIdentifier, IA5String
    41  			Bytes: []byte(url),
    42  		})
    43  	}
    44  
    45  	val := issuingDistributionPoint{
    46  		DistributionPoint:     distributionPointName{FullName: gns},
    47  		OnlyContainsUserCerts: true,
    48  	}
    49  
    50  	valBytes, err := asn1.Marshal(val)
    51  	if err != nil {
    52  		return pkix.Extension{}, err
    53  	}
    54  
    55  	return pkix.Extension{
    56  		Id:       idpOID,
    57  		Value:    valBytes,
    58  		Critical: true,
    59  	}, nil
    60  }
    61  
    62  // MakeCACertsExt returns a critical IssuingDistributionPoint extension
    63  // asserting the OnlyContainsCACerts boolean.
    64  func MakeCACertsExt() (*pkix.Extension, error) {
    65  	val := issuingDistributionPoint{
    66  		OnlyContainsCACerts: true,
    67  	}
    68  
    69  	valBytes, err := asn1.Marshal(val)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	return &pkix.Extension{
    75  		Id:       idpOID,
    76  		Value:    valBytes,
    77  		Critical: true,
    78  	}, nil
    79  }
    80  
    81  // GetIDPURIs returns the URIs contained within the issuingDistributionPoint
    82  // extension, if present, or an error otherwise.
    83  func GetIDPURIs(exts []pkix.Extension) ([]string, error) {
    84  	for _, ext := range exts {
    85  		if ext.Id.Equal(idpOID) {
    86  			val := issuingDistributionPoint{}
    87  			rest, err := asn1.Unmarshal(ext.Value, &val)
    88  			if err != nil {
    89  				return nil, fmt.Errorf("parsing IssuingDistributionPoint extension: %w", err)
    90  			}
    91  			if len(rest) != 0 {
    92  				return nil, fmt.Errorf("parsing IssuingDistributionPoint extension: got %d unexpected trailing bytes", len(rest))
    93  			}
    94  			var uris []string
    95  			for _, generalName := range val.DistributionPoint.FullName {
    96  				uris = append(uris, string(generalName.Bytes))
    97  			}
    98  			return uris, nil
    99  		}
   100  	}
   101  	return nil, errors.New("no IssuingDistributionPoint extension found")
   102  }