-
-
Notifications
You must be signed in to change notification settings - Fork 608
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Centralize IDP construction and make IDP comparison smarter (#7341)
Change crl-storer to only require that 1 of the IssuingDistributionPoint URIs remain consistent between consecutive CRLs in the same sequence. This allows us to add and remove IDP URIs, so we can change our IDP scheme over time. To facilitate this, also move all code which builds or parses IDP extensions into a single place, so that we don't have to have multiple definitions of the same types and similar code in many places. Fixes #7340 Part of #7296
- Loading branch information
1 parent
c110a3e
commit 7432833
Showing
7 changed files
with
176 additions
and
133 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package idp | ||
|
||
import ( | ||
"crypto/x509/pkix" | ||
"encoding/asn1" | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
var idpOID = asn1.ObjectIdentifier{2, 5, 29, 28} // id-ce-issuingDistributionPoint | ||
|
||
// issuingDistributionPoint represents the ASN.1 IssuingDistributionPoint | ||
// SEQUENCE as defined in RFC 5280 Section 5.2.5. We only use three of the | ||
// fields, so the others are omitted. | ||
type issuingDistributionPoint struct { | ||
DistributionPoint distributionPointName `asn1:"optional,tag:0"` | ||
OnlyContainsUserCerts bool `asn1:"optional,tag:1"` | ||
OnlyContainsCACerts bool `asn1:"optional,tag:2"` | ||
} | ||
|
||
// distributionPointName represents the ASN.1 DistributionPointName CHOICE as | ||
// defined in RFC 5280 Section 4.2.1.13. We only use one of the fields, so the | ||
// others are omitted. | ||
type distributionPointName struct { | ||
// Technically, FullName is of type GeneralNames, which is of type SEQUENCE OF | ||
// GeneralName. But GeneralName itself is of type CHOICE, and the ans1.Marhsal | ||
// function doesn't support marshalling structs to CHOICEs, so we have to use | ||
// asn1.RawValue and encode the GeneralName ourselves. | ||
FullName []asn1.RawValue `asn1:"optional,tag:0"` | ||
} | ||
|
||
// MakeUserCertsExt returns a critical IssuingDistributionPoint extension | ||
// containing the given URLs and with the OnlyContainsUserCerts boolean set to | ||
// true. | ||
func MakeUserCertsExt(urls []string) (pkix.Extension, error) { | ||
var gns []asn1.RawValue | ||
for _, url := range urls { | ||
gns = append(gns, asn1.RawValue{ // GeneralName | ||
Class: 2, // context-specific | ||
Tag: 6, // uniformResourceIdentifier, IA5String | ||
Bytes: []byte(url), | ||
}) | ||
} | ||
|
||
val := issuingDistributionPoint{ | ||
DistributionPoint: distributionPointName{FullName: gns}, | ||
OnlyContainsUserCerts: true, | ||
} | ||
|
||
valBytes, err := asn1.Marshal(val) | ||
if err != nil { | ||
return pkix.Extension{}, err | ||
} | ||
|
||
return pkix.Extension{ | ||
Id: idpOID, | ||
Value: valBytes, | ||
Critical: true, | ||
}, nil | ||
} | ||
|
||
// MakeCACertsExt returns a critical IssuingDistributionPoint extension | ||
// asserting the OnlyContainsCACerts boolean. | ||
func MakeCACertsExt() (*pkix.Extension, error) { | ||
val := issuingDistributionPoint{ | ||
OnlyContainsCACerts: true, | ||
} | ||
|
||
valBytes, err := asn1.Marshal(val) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &pkix.Extension{ | ||
Id: idpOID, | ||
Value: valBytes, | ||
Critical: true, | ||
}, nil | ||
} | ||
|
||
// GetIDPURIs returns the URIs contained within the issuingDistributionPoint | ||
// extension, if present, or an error otherwise. | ||
func GetIDPURIs(exts []pkix.Extension) ([]string, error) { | ||
for _, ext := range exts { | ||
if ext.Id.Equal(idpOID) { | ||
val := issuingDistributionPoint{} | ||
rest, err := asn1.Unmarshal(ext.Value, &val) | ||
if err != nil { | ||
return nil, fmt.Errorf("parsing IssuingDistributionPoint extension: %w", err) | ||
} | ||
if len(rest) != 0 { | ||
return nil, fmt.Errorf("parsing IssuingDistributionPoint extension: got %d unexpected trailing bytes", len(rest)) | ||
} | ||
var uris []string | ||
for _, generalName := range val.DistributionPoint.FullName { | ||
uris = append(uris, string(generalName.Bytes)) | ||
} | ||
return uris, nil | ||
} | ||
} | ||
return nil, errors.New("no IssuingDistributionPoint extension found") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package idp | ||
|
||
import ( | ||
"encoding/hex" | ||
"testing" | ||
|
||
"github.com/letsencrypt/boulder/test" | ||
) | ||
|
||
func TestMakeUserCertsExt(t *testing.T) { | ||
t.Parallel() | ||
dehex := func(s string) []byte { r, _ := hex.DecodeString(s); return r } | ||
tests := []struct { | ||
name string | ||
urls []string | ||
want []byte | ||
}{ | ||
{ | ||
name: "one (real) url", | ||
urls: []string{"http://prod.c.lencr.org/20506757847264211/126.crl"}, | ||
want: dehex("303AA035A0338631687474703A2F2F70726F642E632E6C656E63722E6F72672F32303530363735373834373236343231312F3132362E63726C8101FF"), | ||
}, | ||
{ | ||
name: "two urls", | ||
urls: []string{"http://old.style/12345678/90.crl", "http://new.style/90.crl"}, | ||
want: dehex("3042A03DA03B8620687474703A2F2F6F6C642E7374796C652F31323334353637382F39302E63726C8617687474703A2F2F6E65772E7374796C652F39302E63726C8101FF"), | ||
}, | ||
} | ||
for _, tc := range tests { | ||
tc := tc | ||
t.Run(tc.name, func(t *testing.T) { | ||
t.Parallel() | ||
got, err := MakeUserCertsExt(tc.urls) | ||
test.AssertNotError(t, err, "should never fail to marshal asn1 to bytes") | ||
test.AssertDeepEquals(t, got.Id, idpOID) | ||
test.AssertEquals(t, got.Critical, true) | ||
test.AssertDeepEquals(t, got.Value, tc.want) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.