identifier, policy, va: Remove/reject scope zone from IPv6 addresses (#8294)
Followup to #8293 Fixes #8292
This commit is contained in:
parent
5b380adb53
commit
c1ce0c83d0
|
|
@ -118,7 +118,7 @@ func NewIP(ip netip.Addr) ACMEIdentifier {
|
|||
// RFC 8738, Sec. 3: The identifier value MUST contain the textual form
|
||||
// of the address as defined in RFC 1123, Sec. 2.1 for IPv4 and in RFC
|
||||
// 5952, Sec. 4 for IPv6.
|
||||
Value: ip.String(),
|
||||
Value: ip.WithZone("").String(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,39 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestNewIP(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
ip netip.Addr
|
||||
want ACMEIdentifier
|
||||
}{
|
||||
{
|
||||
name: "IPv4 address",
|
||||
ip: netip.MustParseAddr("9.9.9.9"),
|
||||
want: ACMEIdentifier{Type: TypeIP, Value: "9.9.9.9"},
|
||||
},
|
||||
{
|
||||
name: "IPv6 address",
|
||||
ip: netip.MustParseAddr("fe80::cafe"),
|
||||
want: ACMEIdentifier{Type: TypeIP, Value: "fe80::cafe"},
|
||||
},
|
||||
{
|
||||
name: "IPv6 address with scope zone",
|
||||
ip: netip.MustParseAddr("fe80::cafe%lo"),
|
||||
want: ACMEIdentifier{Type: TypeIP, Value: "fe80::cafe"},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := NewIP(tc.ip)
|
||||
if got != tc.want {
|
||||
t.Errorf("NewIP(%#v) = %#v, but want %#v", tc.ip, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFromX509 tests FromCert and FromCSR, which are fromX509's public
|
||||
// wrappers.
|
||||
func TestFromX509(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ func ValidDomain(domain string) error {
|
|||
// ValidIP checks that an IP address:
|
||||
// - isn't empty
|
||||
// - is an IPv4 or IPv6 address
|
||||
// - doesn't contain a scope zone (RFC 4007)
|
||||
// - isn't in an IANA special-purpose address registry
|
||||
//
|
||||
// It does NOT ensure that the IP address is absent from any PA blocked lists.
|
||||
|
|
@ -340,7 +341,7 @@ func ValidIP(ip string) error {
|
|||
// 5952, Sec. 4 for IPv6.") ParseAddr() will accept a non-compliant but
|
||||
// otherwise valid string; String() will output a compliant string.
|
||||
parsedIP, err := netip.ParseAddr(ip)
|
||||
if err != nil || parsedIP.String() != ip {
|
||||
if err != nil || parsedIP.WithZone("").String() != ip {
|
||||
return errIPInvalid
|
||||
}
|
||||
|
||||
|
|
@ -477,6 +478,7 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
|
|||
//
|
||||
// For IP identifiers:
|
||||
// - MUST match the syntax of an IP address
|
||||
// - MUST NOT contain a scope zone (RFC 4007)
|
||||
// - MUST NOT be in an IANA special-purpose address registry
|
||||
//
|
||||
// If multiple identifiers are invalid, the error will contain suberrors
|
||||
|
|
|
|||
|
|
@ -136,6 +136,8 @@ func TestWellFormedIdentifiers(t *testing.T) {
|
|||
{identifier.ACMEIdentifier{Type: "ip", Value: `1.1.168.192.in-addr.arpa`}, errIPInvalid}, // reverse DNS
|
||||
|
||||
// Unexpected IPv6 variants
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `2602:80a:6000:abad:cafe::1%lo`}, errIPInvalid}, // scope zone (RFC 4007)
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `2602:80a:6000:abad:cafe::1%`}, errIPInvalid}, // empty scope zone (RFC 4007)
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:c0ff:ee:a:bad:deed:ffff`}, errIPInvalid}, // extra octet
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:c0ff:ee:a:bad:mead`}, errIPInvalid}, // character out of range
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `2001:db8::/32`}, errIPInvalid}, // with CIDR
|
||||
|
|
|
|||
|
|
@ -338,6 +338,10 @@ func (va *ValidationAuthorityImpl) extractRequestTarget(req *http.Request) (iden
|
|||
|
||||
reqIP, err := netip.ParseAddr(reqHost)
|
||||
if err == nil {
|
||||
// Reject IPv6 addresses with a scope zone (RFCs 4007 & 6874)
|
||||
if reqIP.Zone() != "" {
|
||||
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target: contains scope zone")
|
||||
}
|
||||
err := va.isReservedIPFunc(reqIP)
|
||||
if err != nil {
|
||||
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target: %s", err)
|
||||
|
|
|
|||
|
|
@ -360,6 +360,14 @@ func TestExtractRequestTarget(t *testing.T) {
|
|||
ExpectedError: fmt.Errorf("Invalid host in redirect target: " +
|
||||
"IP address is in a reserved address block: [RFC9637]: Documentation"),
|
||||
},
|
||||
{
|
||||
Name: "bare IPv6, scope zone",
|
||||
Req: &http.Request{
|
||||
URL: mustURL("http://[::1%25lo]"),
|
||||
},
|
||||
ExpectedError: fmt.Errorf("Invalid host in redirect target: " +
|
||||
"contains scope zone"),
|
||||
},
|
||||
{
|
||||
Name: "valid HTTP redirect, explicit port",
|
||||
Req: &http.Request{
|
||||
|
|
|
|||
Loading…
Reference in New Issue