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 }