va: Support IP address identifiers (#8020)
Add an `identifier` field to the `va.PerformValidationRequest` proto, which will soon replace its `dnsName` field. Accept and prefer the `identifier` field in every VA function that uses this struct. Don't (yet) assume it will be present. Throughout the VA, accept and handle the IP address identifier type. Handling is similar to DNS names, except that `getAddrs` is not called, and consider that: - IPs are represented in a different field in the `x509.Certificate` struct. - IPs must be presented as reverse DNS (`.arpa`) names in SNI for [TLS-ALPN-01 challenge requests](https://datatracker.ietf.org/doc/html/rfc8738#name-tls-with-application-layer-). - IPv6 addresses are enclosed in square brackets when composing or parsing URLs. For HTTP-01 challenges, accept redirects to bare IP addresses, which were previously rejected. Fixes #2706 Part of #7311
This commit is contained in:
parent
5822ba3c20
commit
3e6a8e2d25
184
bdns/dns.go
184
bdns/dns.go
|
@ -34,120 +34,71 @@ func parseCidr(network string, comment string) net.IPNet {
|
|||
}
|
||||
|
||||
var (
|
||||
// Private CIDRs to ignore
|
||||
privateNetworks = []net.IPNet{
|
||||
// RFC1918
|
||||
// 10.0.0.0/8
|
||||
{
|
||||
IP: []byte{10, 0, 0, 0},
|
||||
Mask: []byte{255, 0, 0, 0},
|
||||
},
|
||||
// 172.16.0.0/12
|
||||
{
|
||||
IP: []byte{172, 16, 0, 0},
|
||||
Mask: []byte{255, 240, 0, 0},
|
||||
},
|
||||
// 192.168.0.0/16
|
||||
{
|
||||
IP: []byte{192, 168, 0, 0},
|
||||
Mask: []byte{255, 255, 0, 0},
|
||||
},
|
||||
// RFC5735
|
||||
// 127.0.0.0/8
|
||||
{
|
||||
IP: []byte{127, 0, 0, 0},
|
||||
Mask: []byte{255, 0, 0, 0},
|
||||
},
|
||||
// RFC1122 Section 3.2.1.3
|
||||
// 0.0.0.0/8
|
||||
{
|
||||
IP: []byte{0, 0, 0, 0},
|
||||
Mask: []byte{255, 0, 0, 0},
|
||||
},
|
||||
// RFC3927
|
||||
// 169.254.0.0/16
|
||||
{
|
||||
IP: []byte{169, 254, 0, 0},
|
||||
Mask: []byte{255, 255, 0, 0},
|
||||
},
|
||||
// RFC 5736
|
||||
// 192.0.0.0/24
|
||||
{
|
||||
IP: []byte{192, 0, 0, 0},
|
||||
Mask: []byte{255, 255, 255, 0},
|
||||
},
|
||||
// RFC 5737
|
||||
// 192.0.2.0/24
|
||||
{
|
||||
IP: []byte{192, 0, 2, 0},
|
||||
Mask: []byte{255, 255, 255, 0},
|
||||
},
|
||||
// 198.51.100.0/24
|
||||
{
|
||||
IP: []byte{198, 51, 100, 0},
|
||||
Mask: []byte{255, 255, 255, 0},
|
||||
},
|
||||
// 203.0.113.0/24
|
||||
{
|
||||
IP: []byte{203, 0, 113, 0},
|
||||
Mask: []byte{255, 255, 255, 0},
|
||||
},
|
||||
// RFC 3068
|
||||
// 192.88.99.0/24
|
||||
{
|
||||
IP: []byte{192, 88, 99, 0},
|
||||
Mask: []byte{255, 255, 255, 0},
|
||||
},
|
||||
// RFC 2544, Errata 423
|
||||
// 198.18.0.0/15
|
||||
{
|
||||
IP: []byte{198, 18, 0, 0},
|
||||
Mask: []byte{255, 254, 0, 0},
|
||||
},
|
||||
// RFC 3171
|
||||
// 224.0.0.0/4
|
||||
{
|
||||
IP: []byte{224, 0, 0, 0},
|
||||
Mask: []byte{240, 0, 0, 0},
|
||||
},
|
||||
// RFC 1112
|
||||
// 240.0.0.0/4
|
||||
{
|
||||
IP: []byte{240, 0, 0, 0},
|
||||
Mask: []byte{240, 0, 0, 0},
|
||||
},
|
||||
// RFC 919 Section 7
|
||||
// 255.255.255.255/32
|
||||
{
|
||||
IP: []byte{255, 255, 255, 255},
|
||||
Mask: []byte{255, 255, 255, 255},
|
||||
},
|
||||
// RFC 6598
|
||||
// 100.64.0.0/10
|
||||
{
|
||||
IP: []byte{100, 64, 0, 0},
|
||||
Mask: []byte{255, 192, 0, 0},
|
||||
},
|
||||
// TODO(#8040): Rebuild these as structs that track the structure of IANA's
|
||||
// CSV files, for better automated handling.
|
||||
//
|
||||
// Private CIDRs to ignore. Sourced from:
|
||||
// https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
|
||||
privateV4Networks = []net.IPNet{
|
||||
parseCidr("0.0.0.0/8", "RFC 791, Section 3.2: This network"),
|
||||
parseCidr("0.0.0.0/32", "RFC 1122, Section 3.2.1.3: This host on this network"),
|
||||
parseCidr("10.0.0.0/8", "RFC 1918: Private-Use"),
|
||||
parseCidr("100.64.0.0/10", "RFC 6598: Shared Address Space"),
|
||||
parseCidr("127.0.0.0/8", "RFC 1122, Section 3.2.1.3: Loopback"),
|
||||
parseCidr("169.254.0.0/16", "RFC 3927: Link Local"),
|
||||
parseCidr("172.16.0.0/12", "RFC 1918: Private-Use"),
|
||||
parseCidr("192.0.0.0/24", "RFC 6890, Section 2.1: IETF Protocol Assignments"),
|
||||
parseCidr("192.0.0.0/29", "RFC 7335: IPv4 Service Continuity Prefix"),
|
||||
parseCidr("192.0.0.8/32", "RFC 7600: IPv4 dummy address"),
|
||||
parseCidr("192.0.0.9/32", "RFC 7723: Port Control Protocol Anycast"),
|
||||
parseCidr("192.0.0.10/32", "RFC 8155: Traversal Using Relays around NAT Anycast"),
|
||||
parseCidr("192.0.0.170/32", "RFC 8880 & RFC 7050, Section 2.2: NAT64/DNS64 Discovery"),
|
||||
parseCidr("192.0.0.171/32", "RFC 8880 & RFC 7050, Section 2.2: NAT64/DNS64 Discovery"),
|
||||
parseCidr("192.0.2.0/24", "RFC 5737: Documentation (TEST-NET-1)"),
|
||||
parseCidr("192.31.196.0/24", "RFC 7535: AS112-v4"),
|
||||
parseCidr("192.52.193.0/24", "RFC 7450: AMT"),
|
||||
parseCidr("192.88.99.0/24", "RFC 7526: Deprecated (6to4 Relay Anycast)"),
|
||||
parseCidr("192.168.0.0/16", "RFC 1918: Private-Use"),
|
||||
parseCidr("192.175.48.0/24", "RFC 7534: Direct Delegation AS112 Service"),
|
||||
parseCidr("198.18.0.0/15", "RFC 2544: Benchmarking"),
|
||||
parseCidr("198.51.100.0/24", "RFC 5737: Documentation (TEST-NET-2)"),
|
||||
parseCidr("203.0.113.0/24", "RFC 5737: Documentation (TEST-NET-3)"),
|
||||
parseCidr("240.0.0.0/4", "RFC1112, Section 4: Reserved"),
|
||||
parseCidr("255.255.255.255/32", "RFC 8190 & RFC 919, Section 7: Limited Broadcast"),
|
||||
// 224.0.0.0/4 are multicast addresses as per RFC 3171. They are not
|
||||
// present in the IANA registry.
|
||||
parseCidr("224.0.0.0/4", "RFC 3171: Multicast Addresses"),
|
||||
}
|
||||
// Sourced from https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
|
||||
// where Global, Source, or Destination is False
|
||||
// Sourced from:
|
||||
// https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
|
||||
privateV6Networks = []net.IPNet{
|
||||
parseCidr("::/128", "RFC 4291: Unspecified Address"),
|
||||
parseCidr("::1/128", "RFC 4291: Loopback Address"),
|
||||
parseCidr("::ffff:0:0/96", "RFC 4291: IPv4-mapped Address"),
|
||||
parseCidr("100::/64", "RFC 6666: Discard Address Block"),
|
||||
parseCidr("64:ff9b::/96", "RFC 6052: IPv4-IPv6 Translat."),
|
||||
parseCidr("64:ff9b:1::/48", "RFC 8215: IPv4-IPv6 Translat."),
|
||||
parseCidr("100::/64", "RFC 6666: Discard-Only Address Block"),
|
||||
parseCidr("2001::/23", "RFC 2928: IETF Protocol Assignments"),
|
||||
parseCidr("2001:2::/48", "RFC 5180: Benchmarking"),
|
||||
parseCidr("2001::/32", "RFC 4380 & RFC 8190: TEREDO"),
|
||||
parseCidr("2001:1::1/128", "RFC 7723: Port Control Protocol Anycast"),
|
||||
parseCidr("2001:1::2/128", "RFC 8155: Traversal Using Relays around NAT Anycast"),
|
||||
parseCidr("2001:1::3/128", "RFC-ietf-dnssd-srp-25: DNS-SD Service Registration Protocol Anycast"),
|
||||
parseCidr("2001:2::/48", "RFC 5180 & RFC Errata 1752: Benchmarking"),
|
||||
parseCidr("2001:3::/32", "RFC 7450: AMT"),
|
||||
parseCidr("2001:4:112::/48", "RFC 7535: AS112-v6"),
|
||||
parseCidr("2001:10::/28", "RFC 4843: Deprecated (previously ORCHID)"),
|
||||
parseCidr("2001:20::/28", "RFC 7343: ORCHIDv2"),
|
||||
parseCidr("2001:30::/28", "RFC 9374: Drone Remote ID Protocol Entity Tags (DETs) Prefix"),
|
||||
parseCidr("2001:db8::/32", "RFC 3849: Documentation"),
|
||||
parseCidr("2001::/32", "RFC 4380: TEREDO"),
|
||||
parseCidr("fc00::/7", "RFC 4193: Unique-Local"),
|
||||
parseCidr("fe80::/10", "RFC 4291: Section 2.5.6 Link-Scoped Unicast"),
|
||||
parseCidr("ff00::/8", "RFC 4291: Section 2.7"),
|
||||
// We disable validations to IPs under the 6to4 anycast prefix because
|
||||
// there's too much risk of a malicious actor advertising the prefix and
|
||||
// answering validations for a 6to4 host they do not control.
|
||||
// https://community.letsencrypt.org/t/problems-validating-ipv6-against-host-running-6to4/18312/9
|
||||
parseCidr("2002::/16", "RFC 7526: 6to4 anycast prefix deprecated"),
|
||||
parseCidr("2002::/16", "RFC 3056: 6to4"),
|
||||
parseCidr("2620:4f:8000::/48", "RFC 7534: Direct Delegation AS112 Service"),
|
||||
parseCidr("3fff::/20", "RFC 9637: Documentation"),
|
||||
parseCidr("5f00::/16", "RFC 9602: Segment Routing (SRv6) SIDs"),
|
||||
parseCidr("fc00::/7", "RFC 4193 & RFC 8190: Unique-Local"),
|
||||
parseCidr("fe80::/10", "RFC 4291: Link-Local Unicast"),
|
||||
// ff00::/8 are multicast addresses as per RFC 4291, Sections 2.4 & 2.7.
|
||||
// They are not present in the IANA registry.
|
||||
parseCidr("ff00::/8", "RFC 4291: Multicast Addresses"),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -480,7 +431,7 @@ func (dnsClient *impl) LookupTXT(ctx context.Context, hostname string) ([]string
|
|||
}
|
||||
|
||||
func isPrivateV4(ip net.IP) bool {
|
||||
for _, net := range privateNetworks {
|
||||
for _, net := range privateV4Networks {
|
||||
if net.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
|
@ -729,3 +680,20 @@ func (d *dohExchanger) Exchange(query *dns.Msg, server string) (*dns.Msg, time.D
|
|||
|
||||
return response, d.clk.Since(start), nil
|
||||
}
|
||||
|
||||
// IsReservedIP reports whether an IP address is part of a reserved range.
|
||||
//
|
||||
// TODO(#7311): Once we're fully ready to issue for IP address identifiers, dev
|
||||
// environments should have a way to bypass this check for their own Private-Use
|
||||
// IP addresses. Maybe plumb the DNSAllowLoopbackAddresses feature flag through
|
||||
// to here.
|
||||
//
|
||||
// TODO(#8040): Move this and its dependencies into the policy package. As part
|
||||
// of this, consider changing it to return an error and/or the description of
|
||||
// the reserved network.
|
||||
func IsReservedIP(ip net.IP) bool {
|
||||
if ip.To4() == nil {
|
||||
return isPrivateV6(ip)
|
||||
}
|
||||
return isPrivateV4(ip)
|
||||
}
|
||||
|
|
|
@ -149,7 +149,8 @@ func main() {
|
|||
logger,
|
||||
c.VA.AccountURIPrefixes,
|
||||
va.PrimaryPerspective,
|
||||
"")
|
||||
"",
|
||||
bdns.IsReservedIP)
|
||||
cmd.FailOnError(err, "Unable to create VA server")
|
||||
|
||||
start, err := bgrpc.NewServer(c.VA.GRPC, logger).Add(
|
||||
|
|
|
@ -138,7 +138,8 @@ func main() {
|
|||
logger,
|
||||
c.RVA.AccountURIPrefixes,
|
||||
c.RVA.Perspective,
|
||||
c.RVA.RIR)
|
||||
c.RVA.RIR,
|
||||
bdns.IsReservedIP)
|
||||
cmd.FailOnError(err, "Unable to create Remote-VA server")
|
||||
|
||||
start, err := bgrpc.NewServer(c.RVA.GRPC, logger).Add(
|
||||
|
|
|
@ -122,6 +122,8 @@ type ValidationRecord struct {
|
|||
URL string `json:"url,omitempty"`
|
||||
|
||||
// Shared
|
||||
//
|
||||
// TODO(#7311): Replace DnsName with Identifier.
|
||||
DnsName string `json:"hostname,omitempty"`
|
||||
Port string `json:"port,omitempty"`
|
||||
AddressesResolved []net.IP `json:"addressesResolved,omitempty"`
|
||||
|
|
|
@ -39,6 +39,22 @@ func (i ACMEIdentifier) AsProto() *corepb.Identifier {
|
|||
}
|
||||
}
|
||||
|
||||
func FromProto(ident *corepb.Identifier) ACMEIdentifier {
|
||||
return ACMEIdentifier{
|
||||
Type: IdentifierType(ident.Type),
|
||||
Value: ident.Value,
|
||||
}
|
||||
}
|
||||
|
||||
// FromProtoWithDefault can be removed after DnsNames are no longer used in
|
||||
// RPCs. TODO(#8023)
|
||||
func FromProtoWithDefault(ident *corepb.Identifier, name string) ACMEIdentifier {
|
||||
if ident == nil {
|
||||
return NewDNS(name)
|
||||
}
|
||||
return FromProto(ident)
|
||||
}
|
||||
|
||||
// NewDNS is a convenience function for creating an ACMEIdentifier with Type
|
||||
// "dns" for a given domain name.
|
||||
func NewDNS(domain string) ACMEIdentifier {
|
||||
|
@ -52,7 +68,10 @@ func NewDNS(domain string) ACMEIdentifier {
|
|||
// for a given IP address.
|
||||
func NewIP(ip netip.Addr) ACMEIdentifier {
|
||||
return ACMEIdentifier{
|
||||
Type: TypeIP,
|
||||
Value: ip.StringExpanded(),
|
||||
Type: TypeIP,
|
||||
// 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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func (va *ValidationAuthorityImpl) DoCAA(ctx context.Context, req *vapb.IsCAAVal
|
|||
logEvent := validationLogEvent{
|
||||
AuthzID: req.AuthzID,
|
||||
Requester: req.AccountURIID,
|
||||
Identifier: req.Domain,
|
||||
Identifier: identifier.NewDNS(req.Domain),
|
||||
}
|
||||
|
||||
challType := core.AcmeChallenge(req.ValidationMethod)
|
||||
|
|
|
@ -1122,18 +1122,18 @@ func TestMultiCAARechecking(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCAAFailure(t *testing.T) {
|
||||
hs := httpSrv(t, expectedToken)
|
||||
hs := httpSrv(t, expectedToken, false)
|
||||
defer hs.Close()
|
||||
|
||||
va, _ := setup(hs, "", nil, caaMockDNS{})
|
||||
|
||||
err := va.checkCAA(ctx, dnsi("reserved.com"), &caaParams{1, core.ChallengeTypeHTTP01})
|
||||
err := va.checkCAA(ctx, identifier.NewDNS("reserved.com"), &caaParams{1, core.ChallengeTypeHTTP01})
|
||||
if err == nil {
|
||||
t.Fatalf("Expected CAA rejection for reserved.com, got success")
|
||||
}
|
||||
test.AssertErrorIs(t, err, berrors.CAA)
|
||||
|
||||
err = va.checkCAA(ctx, dnsi("example.gonetld"), &caaParams{1, core.ChallengeTypeHTTP01})
|
||||
err = va.checkCAA(ctx, identifier.NewDNS("example.gonetld"), &caaParams{1, core.ChallengeTypeHTTP01})
|
||||
if err == nil {
|
||||
t.Fatalf("Expected CAA rejection for gonetld, got success")
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ func availableAddresses(allAddrs []net.IP) (v4 []net.IP, v6 []net.IP) {
|
|||
func (va *ValidationAuthorityImpl) validateDNS01(ctx context.Context, ident identifier.ACMEIdentifier, keyAuthorization string) ([]core.ValidationRecord, error) {
|
||||
if ident.Type != identifier.TypeDNS {
|
||||
va.log.Infof("Identifier type for DNS challenge was not DNS: %s", ident)
|
||||
return nil, berrors.MalformedError("Identifier type for DNS was not itself DNS")
|
||||
return nil, berrors.MalformedError("Identifier type for DNS challenge was not DNS")
|
||||
}
|
||||
|
||||
// Compute the digest of the key authorization file
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -18,7 +19,7 @@ import (
|
|||
|
||||
func TestDNSValidationWrong(t *testing.T) {
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
_, err := va.validateDNS01(context.Background(), dnsi("wrong-dns01.com"), expectedKeyAuthorization)
|
||||
_, err := va.validateDNS01(context.Background(), identifier.NewDNS("wrong-dns01.com"), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("Successful DNS validation with wrong TXT record")
|
||||
}
|
||||
|
@ -29,7 +30,7 @@ func TestDNSValidationWrong(t *testing.T) {
|
|||
func TestDNSValidationWrongMany(t *testing.T) {
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
|
||||
_, err := va.validateDNS01(context.Background(), dnsi("wrong-many-dns01.com"), expectedKeyAuthorization)
|
||||
_, err := va.validateDNS01(context.Background(), identifier.NewDNS("wrong-many-dns01.com"), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("Successful DNS validation with wrong TXT record")
|
||||
}
|
||||
|
@ -40,7 +41,7 @@ func TestDNSValidationWrongMany(t *testing.T) {
|
|||
func TestDNSValidationWrongLong(t *testing.T) {
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
|
||||
_, err := va.validateDNS01(context.Background(), dnsi("long-dns01.com"), expectedKeyAuthorization)
|
||||
_, err := va.validateDNS01(context.Background(), identifier.NewDNS("long-dns01.com"), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("Successful DNS validation with wrong TXT record")
|
||||
}
|
||||
|
@ -51,12 +52,21 @@ func TestDNSValidationWrongLong(t *testing.T) {
|
|||
func TestDNSValidationFailure(t *testing.T) {
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
|
||||
_, err := va.validateDNS01(ctx, dnsi("localhost"), expectedKeyAuthorization)
|
||||
_, err := va.validateDNS01(ctx, identifier.NewDNS("localhost"), expectedKeyAuthorization)
|
||||
prob := detailedError(err)
|
||||
|
||||
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
||||
}
|
||||
|
||||
func TestDNSValidationIP(t *testing.T) {
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
|
||||
_, err := va.validateDNS01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedKeyAuthorization)
|
||||
prob := detailedError(err)
|
||||
|
||||
test.AssertEquals(t, prob.Type, probs.MalformedProblem)
|
||||
}
|
||||
|
||||
func TestDNSValidationInvalid(t *testing.T) {
|
||||
var notDNS = identifier.ACMEIdentifier{
|
||||
Type: identifier.IdentifierType("iris"),
|
||||
|
@ -74,7 +84,7 @@ func TestDNSValidationInvalid(t *testing.T) {
|
|||
func TestDNSValidationServFail(t *testing.T) {
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
|
||||
_, err := va.validateDNS01(ctx, dnsi("servfail.com"), expectedKeyAuthorization)
|
||||
_, err := va.validateDNS01(ctx, identifier.NewDNS("servfail.com"), expectedKeyAuthorization)
|
||||
|
||||
prob := detailedError(err)
|
||||
test.AssertEquals(t, prob.Type, probs.DNSProblem)
|
||||
|
@ -94,7 +104,7 @@ func TestDNSValidationNoServer(t *testing.T) {
|
|||
log,
|
||||
nil)
|
||||
|
||||
_, err = va.validateDNS01(ctx, dnsi("localhost"), expectedKeyAuthorization)
|
||||
_, err = va.validateDNS01(ctx, identifier.NewDNS("localhost"), expectedKeyAuthorization)
|
||||
prob := detailedError(err)
|
||||
test.AssertEquals(t, prob.Type, probs.DNSProblem)
|
||||
}
|
||||
|
@ -102,7 +112,7 @@ func TestDNSValidationNoServer(t *testing.T) {
|
|||
func TestDNSValidationOK(t *testing.T) {
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
|
||||
_, prob := va.validateDNS01(ctx, dnsi("good-dns01.com"), expectedKeyAuthorization)
|
||||
_, prob := va.validateDNS01(ctx, identifier.NewDNS("good-dns01.com"), expectedKeyAuthorization)
|
||||
|
||||
test.Assert(t, prob == nil, "Should be valid.")
|
||||
}
|
||||
|
@ -110,7 +120,7 @@ func TestDNSValidationOK(t *testing.T) {
|
|||
func TestDNSValidationNoAuthorityOK(t *testing.T) {
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
|
||||
_, prob := va.validateDNS01(ctx, dnsi("no-authority-dns01.com"), expectedKeyAuthorization)
|
||||
_, prob := va.validateDNS01(ctx, identifier.NewDNS("no-authority-dns01.com"), expectedKeyAuthorization)
|
||||
|
||||
test.Assert(t, prob == nil, "Should be valid.")
|
||||
}
|
||||
|
|
115
va/http.go
115
va/http.go
|
@ -8,6 +8,7 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -159,7 +160,7 @@ func httpTransport(df dialerFunc) *http.Transport {
|
|||
// httpValidationTarget bundles all of the information needed to make an HTTP-01
|
||||
// validation request against a target.
|
||||
type httpValidationTarget struct {
|
||||
// the hostname being validated
|
||||
// the host being validated
|
||||
host string
|
||||
// the port for the validation request
|
||||
port int
|
||||
|
@ -203,18 +204,28 @@ func (vt *httpValidationTarget) nextIP() error {
|
|||
// lookups fail.
|
||||
func (va *ValidationAuthorityImpl) newHTTPValidationTarget(
|
||||
ctx context.Context,
|
||||
host string,
|
||||
ident identifier.ACMEIdentifier,
|
||||
port int,
|
||||
path string,
|
||||
query string) (*httpValidationTarget, error) {
|
||||
// Resolve IP addresses for the hostname
|
||||
addrs, resolvers, err := va.getAddrs(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var addrs []net.IP
|
||||
var resolvers bdns.ResolverAddrs
|
||||
switch ident.Type {
|
||||
case identifier.TypeDNS:
|
||||
// Resolve IP addresses for the identifier
|
||||
dnsAddrs, dnsResolvers, err := va.getAddrs(ctx, ident.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs, resolvers = dnsAddrs, dnsResolvers
|
||||
case identifier.TypeIP:
|
||||
addrs = []net.IP{net.ParseIP(ident.Value)}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown identifier type: %s", ident.Type)
|
||||
}
|
||||
|
||||
target := &httpValidationTarget{
|
||||
host: host,
|
||||
host: ident.Value,
|
||||
port: port,
|
||||
path: path,
|
||||
query: query,
|
||||
|
@ -230,7 +241,7 @@ func (va *ValidationAuthorityImpl) newHTTPValidationTarget(
|
|||
if !hasV6Addrs && !hasV4Addrs {
|
||||
// If there are no v6 addrs and no v4addrs there was a bug with getAddrs or
|
||||
// availableAddresses and we need to return an error.
|
||||
return nil, fmt.Errorf("host %q has no IPv4 or IPv6 addresses", host)
|
||||
return nil, fmt.Errorf("host %q has no IPv4 or IPv6 addresses", ident.Value)
|
||||
} else if !hasV6Addrs && hasV4Addrs {
|
||||
// If there are no v6 addrs and there are v4 addrs then use the first v4
|
||||
// address. There's no fallback address.
|
||||
|
@ -250,45 +261,44 @@ func (va *ValidationAuthorityImpl) newHTTPValidationTarget(
|
|||
return target, nil
|
||||
}
|
||||
|
||||
// extractRequestTarget extracts the hostname and port specified in the provided
|
||||
// extractRequestTarget extracts the host and port specified in the provided
|
||||
// HTTP redirect request. If the request's URL's protocol schema is not HTTP or
|
||||
// HTTPS an error is returned. If an explicit port is specified in the request's
|
||||
// URL and it isn't the VA's HTTP or HTTPS port, an error is returned. If the
|
||||
// request's URL's Host is a bare IPv4 or IPv6 address and not a domain name an
|
||||
// error is returned.
|
||||
func (va *ValidationAuthorityImpl) extractRequestTarget(req *http.Request) (string, int, error) {
|
||||
// URL and it isn't the VA's HTTP or HTTPS port, an error is returned.
|
||||
func (va *ValidationAuthorityImpl) extractRequestTarget(req *http.Request) (identifier.ACMEIdentifier, int, error) {
|
||||
// A nil request is certainly not a valid redirect and has no port to extract.
|
||||
if req == nil {
|
||||
return "", 0, fmt.Errorf("redirect HTTP request was nil")
|
||||
return identifier.ACMEIdentifier{}, 0, fmt.Errorf("redirect HTTP request was nil")
|
||||
}
|
||||
|
||||
reqScheme := req.URL.Scheme
|
||||
|
||||
// The redirect request must use HTTP or HTTPs protocol schemes regardless of the port..
|
||||
if reqScheme != "http" && reqScheme != "https" {
|
||||
return "", 0, berrors.ConnectionFailureError(
|
||||
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError(
|
||||
"Invalid protocol scheme in redirect target. "+
|
||||
`Only "http" and "https" protocol schemes are supported, not %q`, reqScheme)
|
||||
}
|
||||
|
||||
// Try and split an explicit port number from the request URL host. If there is
|
||||
// one we need to make sure its a valid port. If there isn't one we need to
|
||||
// pick the port based on the reqScheme default port.
|
||||
reqHost := req.URL.Host
|
||||
// Try to parse an explicit port number from the request URL host. If there
|
||||
// is one, we need to make sure its a valid port. If there isn't one we need
|
||||
// to pick the port based on the reqScheme default port.
|
||||
reqHost := req.URL.Hostname()
|
||||
var reqPort int
|
||||
if h, p, err := net.SplitHostPort(reqHost); err == nil {
|
||||
reqHost = h
|
||||
reqPort, err = strconv.Atoi(p)
|
||||
if req.URL.Port() != "" {
|
||||
parsedPort, err := strconv.Atoi(req.URL.Port())
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
return identifier.ACMEIdentifier{}, 0, err
|
||||
}
|
||||
|
||||
// The explicit port must match the VA's configured HTTP or HTTPS port.
|
||||
if reqPort != va.httpPort && reqPort != va.httpsPort {
|
||||
return "", 0, berrors.ConnectionFailureError(
|
||||
if parsedPort != va.httpPort && parsedPort != va.httpsPort {
|
||||
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError(
|
||||
"Invalid port in redirect target. Only ports %d and %d are supported, not %d",
|
||||
va.httpPort, va.httpsPort, reqPort)
|
||||
va.httpPort, va.httpsPort, parsedPort)
|
||||
}
|
||||
|
||||
reqPort = parsedPort
|
||||
} else if reqScheme == "http" {
|
||||
reqPort = va.httpPort
|
||||
} else if reqScheme == "https" {
|
||||
|
@ -296,17 +306,11 @@ func (va *ValidationAuthorityImpl) extractRequestTarget(req *http.Request) (stri
|
|||
} else {
|
||||
// This shouldn't happen but defensively return an internal server error in
|
||||
// case it does.
|
||||
return "", 0, fmt.Errorf("unable to determine redirect HTTP request port")
|
||||
return identifier.ACMEIdentifier{}, 0, fmt.Errorf("unable to determine redirect HTTP request port")
|
||||
}
|
||||
|
||||
if reqHost == "" {
|
||||
return "", 0, berrors.ConnectionFailureError("Invalid empty hostname in redirect target")
|
||||
}
|
||||
|
||||
// Check that the request host isn't a bare IP address. We only follow
|
||||
// redirects to hostnames.
|
||||
if net.ParseIP(reqHost) != nil {
|
||||
return "", 0, berrors.ConnectionFailureError("Invalid host in redirect target %q. Only domain names are supported, not IP addresses", reqHost)
|
||||
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid empty host in redirect target")
|
||||
}
|
||||
|
||||
// Often folks will misconfigure their webserver to send an HTTP redirect
|
||||
|
@ -319,17 +323,25 @@ func (va *ValidationAuthorityImpl) extractRequestTarget(req *http.Request) (stri
|
|||
// This happens frequently enough we want to return a distinct error message
|
||||
// for this case by detecting the reqHost ending in ".well-known".
|
||||
if strings.HasSuffix(reqHost, ".well-known") {
|
||||
return "", 0, berrors.ConnectionFailureError(
|
||||
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError(
|
||||
"Invalid host in redirect target %q. Check webserver config for missing '/' in redirect target.",
|
||||
reqHost,
|
||||
)
|
||||
}
|
||||
|
||||
if _, err := iana.ExtractSuffix(reqHost); err != nil {
|
||||
return "", 0, berrors.ConnectionFailureError("Invalid hostname in redirect target, must end in IANA registered TLD")
|
||||
reqIP, err := netip.ParseAddr(reqHost)
|
||||
if err == nil {
|
||||
if va.isReservedIPFunc(reqIP.AsSlice()) {
|
||||
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target, must not be a reserved IP address")
|
||||
}
|
||||
return identifier.NewIP(reqIP), reqPort, nil
|
||||
}
|
||||
|
||||
return reqHost, reqPort, nil
|
||||
if _, err := iana.ExtractSuffix(reqHost); err != nil {
|
||||
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target, must end in IANA registered TLD")
|
||||
}
|
||||
|
||||
return identifier.NewDNS(reqHost), reqPort, nil
|
||||
}
|
||||
|
||||
// setupHTTPValidation sets up a preresolvedDialer and a validation record for
|
||||
|
@ -371,6 +383,8 @@ func (va *ValidationAuthorityImpl) setupHTTPValidation(
|
|||
"host %q has no IP addresses remaining to use",
|
||||
target.host)
|
||||
}
|
||||
// TODO(#8041): This could be a good place for a backstop check for reserved IP
|
||||
// addresses.
|
||||
record.AddressUsed = targetIP
|
||||
|
||||
dialer := &preresolvedDialer{
|
||||
|
@ -403,14 +417,27 @@ func fallbackErr(err error) bool {
|
|||
// a non-nil error and potentially some ValidationRecords are returned.
|
||||
func (va *ValidationAuthorityImpl) processHTTPValidation(
|
||||
ctx context.Context,
|
||||
host string,
|
||||
ident identifier.ACMEIdentifier,
|
||||
path string) ([]byte, []core.ValidationRecord, error) {
|
||||
// Create a target for the host, port and path with no query parameters
|
||||
target, err := va.newHTTPValidationTarget(ctx, host, va.httpPort, path, "")
|
||||
target, err := va.newHTTPValidationTarget(ctx, ident, va.httpPort, path, "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// When constructing a URL, bare IPv6 addresses must be enclosed in square
|
||||
// brackets. Otherwise, a colon may be interpreted as a port separator.
|
||||
host := ident.Value
|
||||
if ident.Type == identifier.TypeIP {
|
||||
netipHost, err := netip.ParseAddr(host)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("couldn't parse IP address from identifier")
|
||||
}
|
||||
if !netipHost.Is4() {
|
||||
host = "[" + host + "]"
|
||||
}
|
||||
}
|
||||
|
||||
// Create an initial GET Request
|
||||
initialURL := url.URL{
|
||||
Scheme: "http",
|
||||
|
@ -626,14 +653,14 @@ func (va *ValidationAuthorityImpl) processHTTPValidation(
|
|||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) validateHTTP01(ctx context.Context, ident identifier.ACMEIdentifier, token string, keyAuthorization string) ([]core.ValidationRecord, error) {
|
||||
if ident.Type != identifier.TypeDNS {
|
||||
va.log.Infof("Got non-DNS identifier for HTTP validation: %s", ident)
|
||||
return nil, berrors.MalformedError("Identifier type for HTTP validation was not DNS")
|
||||
if ident.Type != identifier.TypeDNS && ident.Type != identifier.TypeIP {
|
||||
va.log.Info(fmt.Sprintf("Identifier type for HTTP-01 challenge was not DNS or IP: %s", ident))
|
||||
return nil, berrors.MalformedError("Identifier type for HTTP-01 challenge was not DNS or IP")
|
||||
}
|
||||
|
||||
// Perform the fetch
|
||||
path := fmt.Sprintf(".well-known/acme-challenge/%s", token)
|
||||
body, validationRecords, err := va.processHTTPValidation(ctx, ident.Value, "/"+path)
|
||||
body, validationRecords, err := va.processHTTPValidation(ctx, ident, "/"+path)
|
||||
if err != nil {
|
||||
return validationRecords, err
|
||||
}
|
||||
|
|
504
va/http_test.go
504
va/http_test.go
File diff suppressed because it is too large
Load Diff
|
@ -26,6 +26,9 @@ type IsCAAValidRequest struct {
|
|||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// TODO: Accept an identifier instead of a domain (purely for consistency,
|
||||
// because only DNS identifiers support CAA checks).
|
||||
//
|
||||
// NOTE: Domain may be a name with a wildcard prefix (e.g. `*.example.com`)
|
||||
Domain string `protobuf:"bytes,1,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||
ValidationMethod string `protobuf:"bytes,2,opt,name=validationMethod,proto3" json:"validationMethod,omitempty"`
|
||||
|
@ -162,10 +165,13 @@ type PerformValidationRequest struct {
|
|||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
DnsName string `protobuf:"bytes,1,opt,name=dnsName,proto3" json:"dnsName,omitempty"`
|
||||
Challenge *proto.Challenge `protobuf:"bytes,2,opt,name=challenge,proto3" json:"challenge,omitempty"`
|
||||
Authz *AuthzMeta `protobuf:"bytes,3,opt,name=authz,proto3" json:"authz,omitempty"`
|
||||
ExpectedKeyAuthorization string `protobuf:"bytes,4,opt,name=expectedKeyAuthorization,proto3" json:"expectedKeyAuthorization,omitempty"`
|
||||
// Next unused field number: 6
|
||||
// TODO(#8023): dnsNames are being deprecated in favour of identifiers.
|
||||
DnsName string `protobuf:"bytes,1,opt,name=dnsName,proto3" json:"dnsName,omitempty"`
|
||||
Identifier *proto.Identifier `protobuf:"bytes,5,opt,name=identifier,proto3" json:"identifier,omitempty"`
|
||||
Challenge *proto.Challenge `protobuf:"bytes,2,opt,name=challenge,proto3" json:"challenge,omitempty"`
|
||||
Authz *AuthzMeta `protobuf:"bytes,3,opt,name=authz,proto3" json:"authz,omitempty"`
|
||||
ExpectedKeyAuthorization string `protobuf:"bytes,4,opt,name=expectedKeyAuthorization,proto3" json:"expectedKeyAuthorization,omitempty"`
|
||||
}
|
||||
|
||||
func (x *PerformValidationRequest) Reset() {
|
||||
|
@ -207,6 +213,13 @@ func (x *PerformValidationRequest) GetDnsName() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (x *PerformValidationRequest) GetIdentifier() *proto.Identifier {
|
||||
if x != nil {
|
||||
return x.Identifier
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *PerformValidationRequest) GetChallenge() *proto.Challenge {
|
||||
if x != nil {
|
||||
return x.Challenge
|
||||
|
@ -376,45 +389,48 @@ var file_va_proto_rawDesc = []byte{
|
|||
0x6c, 0x65, 0x6d, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69,
|
||||
0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x73, 0x70, 0x65,
|
||||
0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x03, 0x72, 0x69, 0x72, 0x22, 0xc4, 0x01, 0x0a, 0x18, 0x50, 0x65, 0x72, 0x66,
|
||||
0x28, 0x09, 0x52, 0x03, 0x72, 0x69, 0x72, 0x22, 0xf6, 0x01, 0x0a, 0x18, 0x50, 0x65, 0x72, 0x66,
|
||||
0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2d,
|
||||
0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e,
|
||||
0x67, 0x65, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x23, 0x0a,
|
||||
0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76,
|
||||
0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x61, 0x75, 0x74,
|
||||
0x68, 0x7a, 0x12, 0x3a, 0x0a, 0x18, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4b, 0x65,
|
||||
0x79, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4b, 0x65,
|
||||
0x79, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x31,
|
||||
0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x72,
|
||||
0x65, 0x67, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x72, 0x65, 0x67, 0x49,
|
||||
0x44, 0x22, 0xa8, 0x01, 0x0a, 0x10, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x30, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x56,
|
||||
0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52,
|
||||
0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2e, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x62,
|
||||
0x6c, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x2e, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52,
|
||||
0x07, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x73,
|
||||
0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70,
|
||||
0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x69,
|
||||
0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x69, 0x72, 0x32, 0x43, 0x0a, 0x02,
|
||||
0x56, 0x41, 0x12, 0x3d, 0x0a, 0x05, 0x44, 0x6f, 0x44, 0x43, 0x56, 0x12, 0x1c, 0x2e, 0x76, 0x61,
|
||||
0x2e, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x76, 0x61, 0x2e, 0x56,
|
||||
0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22,
|
||||
0x00, 0x32, 0x3f, 0x0a, 0x03, 0x43, 0x41, 0x41, 0x12, 0x38, 0x0a, 0x05, 0x44, 0x6f, 0x43, 0x41,
|
||||
0x41, 0x12, 0x15, 0x2e, 0x76, 0x61, 0x2e, 0x49, 0x73, 0x43, 0x41, 0x41, 0x56, 0x61, 0x6c, 0x69,
|
||||
0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x76, 0x61, 0x2e, 0x49, 0x73,
|
||||
0x43, 0x41, 0x41, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75,
|
||||
0x6c, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x30,
|
||||
0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
|
||||
0x66, 0x69, 0x65, 0x72, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72,
|
||||
0x12, 0x2d, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6c, 0x6c,
|
||||
0x65, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12,
|
||||
0x23, 0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d,
|
||||
0x2e, 0x76, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x61,
|
||||
0x75, 0x74, 0x68, 0x7a, 0x12, 0x3a, 0x0a, 0x18, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64,
|
||||
0x4b, 0x65, 0x79, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64,
|
||||
0x4b, 0x65, 0x79, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x22, 0x31, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x0e, 0x0a,
|
||||
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x72, 0x65, 0x67, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x72, 0x65,
|
||||
0x67, 0x49, 0x44, 0x22, 0xa8, 0x01, 0x0a, 0x10, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x30, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f,
|
||||
0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72,
|
||||
0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2e, 0x0a, 0x07, 0x70, 0x72,
|
||||
0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f,
|
||||
0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c,
|
||||
0x73, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65,
|
||||
0x72, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0b, 0x70, 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x10, 0x0a, 0x03,
|
||||
0x72, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x69, 0x72, 0x32, 0x43,
|
||||
0x0a, 0x02, 0x56, 0x41, 0x12, 0x3d, 0x0a, 0x05, 0x44, 0x6f, 0x44, 0x43, 0x56, 0x12, 0x1c, 0x2e,
|
||||
0x76, 0x61, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x76, 0x61,
|
||||
0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c,
|
||||
0x74, 0x22, 0x00, 0x32, 0x3f, 0x0a, 0x03, 0x43, 0x41, 0x41, 0x12, 0x38, 0x0a, 0x05, 0x44, 0x6f,
|
||||
0x43, 0x41, 0x41, 0x12, 0x15, 0x2e, 0x76, 0x61, 0x2e, 0x49, 0x73, 0x43, 0x41, 0x41, 0x56, 0x61,
|
||||
0x6c, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x76, 0x61, 0x2e,
|
||||
0x49, 0x73, 0x43, 0x41, 0x41, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62,
|
||||
0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -437,24 +453,26 @@ var file_va_proto_goTypes = []interface{}{
|
|||
(*AuthzMeta)(nil), // 3: va.AuthzMeta
|
||||
(*ValidationResult)(nil), // 4: va.ValidationResult
|
||||
(*proto.ProblemDetails)(nil), // 5: core.ProblemDetails
|
||||
(*proto.Challenge)(nil), // 6: core.Challenge
|
||||
(*proto.ValidationRecord)(nil), // 7: core.ValidationRecord
|
||||
(*proto.Identifier)(nil), // 6: core.Identifier
|
||||
(*proto.Challenge)(nil), // 7: core.Challenge
|
||||
(*proto.ValidationRecord)(nil), // 8: core.ValidationRecord
|
||||
}
|
||||
var file_va_proto_depIdxs = []int32{
|
||||
5, // 0: va.IsCAAValidResponse.problem:type_name -> core.ProblemDetails
|
||||
6, // 1: va.PerformValidationRequest.challenge:type_name -> core.Challenge
|
||||
3, // 2: va.PerformValidationRequest.authz:type_name -> va.AuthzMeta
|
||||
7, // 3: va.ValidationResult.records:type_name -> core.ValidationRecord
|
||||
5, // 4: va.ValidationResult.problem:type_name -> core.ProblemDetails
|
||||
2, // 5: va.VA.DoDCV:input_type -> va.PerformValidationRequest
|
||||
0, // 6: va.CAA.DoCAA:input_type -> va.IsCAAValidRequest
|
||||
4, // 7: va.VA.DoDCV:output_type -> va.ValidationResult
|
||||
1, // 8: va.CAA.DoCAA:output_type -> va.IsCAAValidResponse
|
||||
7, // [7:9] is the sub-list for method output_type
|
||||
5, // [5:7] is the sub-list for method input_type
|
||||
5, // [5:5] is the sub-list for extension type_name
|
||||
5, // [5:5] is the sub-list for extension extendee
|
||||
0, // [0:5] is the sub-list for field type_name
|
||||
6, // 1: va.PerformValidationRequest.identifier:type_name -> core.Identifier
|
||||
7, // 2: va.PerformValidationRequest.challenge:type_name -> core.Challenge
|
||||
3, // 3: va.PerformValidationRequest.authz:type_name -> va.AuthzMeta
|
||||
8, // 4: va.ValidationResult.records:type_name -> core.ValidationRecord
|
||||
5, // 5: va.ValidationResult.problem:type_name -> core.ProblemDetails
|
||||
2, // 6: va.VA.DoDCV:input_type -> va.PerformValidationRequest
|
||||
0, // 7: va.CAA.DoCAA:input_type -> va.IsCAAValidRequest
|
||||
4, // 8: va.VA.DoDCV:output_type -> va.ValidationResult
|
||||
1, // 9: va.CAA.DoCAA:output_type -> va.IsCAAValidResponse
|
||||
8, // [8:10] is the sub-list for method output_type
|
||||
6, // [6:8] is the sub-list for method input_type
|
||||
6, // [6:6] is the sub-list for extension type_name
|
||||
6, // [6:6] is the sub-list for extension extendee
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_va_proto_init() }
|
||||
|
|
|
@ -14,6 +14,9 @@ service CAA {
|
|||
}
|
||||
|
||||
message IsCAAValidRequest {
|
||||
// TODO: Accept an identifier instead of a domain (purely for consistency,
|
||||
// because only DNS identifiers support CAA checks).
|
||||
//
|
||||
// NOTE: Domain may be a name with a wildcard prefix (e.g. `*.example.com`)
|
||||
string domain = 1;
|
||||
string validationMethod = 2;
|
||||
|
@ -29,7 +32,10 @@ message IsCAAValidResponse {
|
|||
}
|
||||
|
||||
message PerformValidationRequest {
|
||||
// Next unused field number: 6
|
||||
// TODO(#8023): dnsNames are being deprecated in favour of identifiers.
|
||||
string dnsName = 1;
|
||||
core.Identifier identifier = 5;
|
||||
core.Challenge challenge = 2;
|
||||
AuthzMeta authz = 3;
|
||||
string expectedKeyAuthorization = 4;
|
||||
|
|
163
va/tlsalpn.go
163
va/tlsalpn.go
|
@ -13,9 +13,12 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
berrors "github.com/letsencrypt/boulder/errors"
|
||||
"github.com/letsencrypt/boulder/identifier"
|
||||
|
@ -58,28 +61,38 @@ func certAltNames(cert *x509.Certificate) []string {
|
|||
|
||||
func (va *ValidationAuthorityImpl) tryGetChallengeCert(
|
||||
ctx context.Context,
|
||||
identifier identifier.ACMEIdentifier,
|
||||
tlsConfig *tls.Config,
|
||||
ident identifier.ACMEIdentifier,
|
||||
) (*x509.Certificate, *tls.ConnectionState, core.ValidationRecord, error) {
|
||||
|
||||
allAddrs, resolvers, err := va.getAddrs(ctx, identifier.Value)
|
||||
validationRecord := core.ValidationRecord{
|
||||
DnsName: identifier.Value,
|
||||
AddressesResolved: allAddrs,
|
||||
Port: strconv.Itoa(va.tlsPort),
|
||||
ResolverAddrs: resolvers,
|
||||
DnsName: ident.Value,
|
||||
Port: strconv.Itoa(va.tlsPort),
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, validationRecord, err
|
||||
|
||||
var addrs []net.IP
|
||||
switch ident.Type {
|
||||
case identifier.TypeDNS:
|
||||
// Resolve IP addresses for the identifier
|
||||
dnsAddrs, dnsResolvers, err := va.getAddrs(ctx, ident.Value)
|
||||
if err != nil {
|
||||
return nil, nil, validationRecord, err
|
||||
}
|
||||
addrs, validationRecord.ResolverAddrs = dnsAddrs, dnsResolvers
|
||||
validationRecord.AddressesResolved = addrs
|
||||
case identifier.TypeIP:
|
||||
addrs = []net.IP{net.ParseIP(ident.Value)}
|
||||
default:
|
||||
// This should never happen. The calling function should check the
|
||||
// identifier type.
|
||||
return nil, nil, validationRecord, fmt.Errorf("unknown identifier type: %s", ident.Type)
|
||||
}
|
||||
|
||||
// Split the available addresses into v4 and v6 addresses
|
||||
v4, v6 := availableAddresses(allAddrs)
|
||||
v4, v6 := availableAddresses(addrs)
|
||||
addresses := append(v4, v6...)
|
||||
|
||||
// This shouldn't happen, but be defensive about it anyway
|
||||
if len(addresses) < 1 {
|
||||
return nil, nil, validationRecord, berrors.MalformedError("no IP addresses found for %q", identifier.Value)
|
||||
return nil, nil, validationRecord, berrors.MalformedError("no IP addresses found for %q", ident.Value)
|
||||
}
|
||||
|
||||
// If there is at least one IPv6 address then try it first
|
||||
|
@ -87,7 +100,7 @@ func (va *ValidationAuthorityImpl) tryGetChallengeCert(
|
|||
address := net.JoinHostPort(v6[0].String(), validationRecord.Port)
|
||||
validationRecord.AddressUsed = v6[0]
|
||||
|
||||
cert, cs, err := va.getChallengeCert(ctx, address, identifier, tlsConfig)
|
||||
cert, cs, err := va.getChallengeCert(ctx, address, ident)
|
||||
|
||||
// If there is no problem, return immediately
|
||||
if err == nil {
|
||||
|
@ -114,27 +127,50 @@ func (va *ValidationAuthorityImpl) tryGetChallengeCert(
|
|||
// talking to the first IPv6 address, try the first IPv4 address
|
||||
validationRecord.AddressUsed = v4[0]
|
||||
address := net.JoinHostPort(v4[0].String(), validationRecord.Port)
|
||||
cert, cs, err := va.getChallengeCert(ctx, address, identifier, tlsConfig)
|
||||
cert, cs, err := va.getChallengeCert(ctx, address, ident)
|
||||
return cert, cs, validationRecord, err
|
||||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) getChallengeCert(
|
||||
ctx context.Context,
|
||||
hostPort string,
|
||||
identifier identifier.ACMEIdentifier,
|
||||
config *tls.Config,
|
||||
ident identifier.ACMEIdentifier,
|
||||
) (*x509.Certificate, *tls.ConnectionState, error) {
|
||||
va.log.Info(fmt.Sprintf("%s [%s] Attempting to validate for %s %s", core.ChallengeTypeTLSALPN01, identifier, hostPort, config.ServerName))
|
||||
// We expect a self-signed challenge certificate, do not verify it here.
|
||||
config.InsecureSkipVerify = true
|
||||
var serverName string
|
||||
switch ident.Type {
|
||||
case identifier.TypeDNS:
|
||||
serverName = ident.Value
|
||||
case identifier.TypeIP:
|
||||
reverseIP, err := dns.ReverseAddr(ident.Value)
|
||||
if err != nil {
|
||||
va.log.Infof("%s Failed to parse IP address %s.", core.ChallengeTypeTLSALPN01, ident.Value)
|
||||
return nil, nil, fmt.Errorf("failed to parse IP address")
|
||||
}
|
||||
serverName = reverseIP
|
||||
default:
|
||||
// This should never happen. The calling function should check the
|
||||
// identifier type.
|
||||
va.log.Infof("%s Unknown identifier type '%s' for %s.", core.ChallengeTypeTLSALPN01, ident.Type, ident.Value)
|
||||
return nil, nil, fmt.Errorf("unknown identifier type: %s", ident.Type)
|
||||
}
|
||||
|
||||
va.log.Info(fmt.Sprintf("%s [%s] Attempting to validate for %s %s", core.ChallengeTypeTLSALPN01, ident, hostPort, serverName))
|
||||
|
||||
dialCtx, cancel := context.WithTimeout(ctx, va.singleDialTimeout)
|
||||
defer cancel()
|
||||
|
||||
dialer := &tls.Dialer{Config: config}
|
||||
dialer := &tls.Dialer{Config: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{ACMETLS1Protocol},
|
||||
ServerName: serverName,
|
||||
// We expect a self-signed challenge certificate, do not verify it here.
|
||||
InsecureSkipVerify: true,
|
||||
}}
|
||||
// TODO(#8041): This could be a good place for a backstop check for reserved IP
|
||||
// addresses.
|
||||
conn, err := dialer.DialContext(dialCtx, "tcp", hostPort)
|
||||
if err != nil {
|
||||
va.log.Infof("%s connection failure for %s. err=[%#v] errStr=[%s]", core.ChallengeTypeTLSALPN01, identifier, err, err)
|
||||
va.log.Infof("%s connection failure for %s. err=[%#v] errStr=[%s]", core.ChallengeTypeTLSALPN01, ident, err, err)
|
||||
host, _, splitErr := net.SplitHostPort(hostPort)
|
||||
if splitErr == nil && net.ParseIP(host) != nil {
|
||||
// Wrap the validation error and the IP of the remote host in an
|
||||
|
@ -150,36 +186,69 @@ func (va *ValidationAuthorityImpl) getChallengeCert(
|
|||
cs := conn.(*tls.Conn).ConnectionState()
|
||||
certs := cs.PeerCertificates
|
||||
if len(certs) == 0 {
|
||||
va.log.Infof("%s challenge for %s resulted in no certificates", core.ChallengeTypeTLSALPN01, identifier.Value)
|
||||
va.log.Infof("%s challenge for %s resulted in no certificates", core.ChallengeTypeTLSALPN01, ident.Value)
|
||||
return nil, nil, berrors.UnauthorizedError("No certs presented for %s challenge", core.ChallengeTypeTLSALPN01)
|
||||
}
|
||||
for i, cert := range certs {
|
||||
va.log.AuditInfof("%s challenge for %s received certificate (%d of %d): cert=[%s]",
|
||||
core.ChallengeTypeTLSALPN01, identifier.Value, i+1, len(certs), hex.EncodeToString(cert.Raw))
|
||||
core.ChallengeTypeTLSALPN01, ident.Value, i+1, len(certs), hex.EncodeToString(cert.Raw))
|
||||
}
|
||||
return certs[0], &cs, nil
|
||||
}
|
||||
|
||||
func checkExpectedSAN(cert *x509.Certificate, name identifier.ACMEIdentifier) error {
|
||||
if len(cert.DNSNames) != 1 {
|
||||
return errors.New("wrong number of dNSNames")
|
||||
func checkExpectedSAN(cert *x509.Certificate, ident identifier.ACMEIdentifier) error {
|
||||
var expectedSANBytes []byte
|
||||
switch ident.Type {
|
||||
case identifier.TypeDNS:
|
||||
if len(cert.DNSNames) != 1 || len(cert.IPAddresses) != 0 {
|
||||
return errors.New("wrong number of identifiers")
|
||||
}
|
||||
if !strings.EqualFold(cert.DNSNames[0], ident.Value) {
|
||||
return errors.New("identifier does not match expected identifier")
|
||||
}
|
||||
bytes, err := asn1.Marshal([]asn1.RawValue{
|
||||
{Tag: 2, Class: 2, Bytes: []byte(ident.Value)},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("composing SAN extension: %w", err)
|
||||
}
|
||||
expectedSANBytes = bytes
|
||||
case identifier.TypeIP:
|
||||
if len(cert.IPAddresses) != 1 || len(cert.DNSNames) != 0 {
|
||||
return errors.New("wrong number of identifiers")
|
||||
}
|
||||
if !cert.IPAddresses[0].Equal(net.ParseIP(ident.Value)) {
|
||||
return errors.New("identifier does not match expected identifier")
|
||||
}
|
||||
netipAddr, err := netip.ParseAddr(ident.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing IP address identifier: %w", err)
|
||||
}
|
||||
netipBytes, err := netipAddr.MarshalBinary()
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling IP address identifier: %w", err)
|
||||
}
|
||||
bytes, err := asn1.Marshal([]asn1.RawValue{
|
||||
{Tag: 7, Class: 2, Bytes: netipBytes},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("composing SAN extension: %w", err)
|
||||
}
|
||||
expectedSANBytes = bytes
|
||||
default:
|
||||
// This should never happen. The calling function should check the
|
||||
// identifier type.
|
||||
return fmt.Errorf("unknown identifier type: %s", ident.Type)
|
||||
}
|
||||
|
||||
for _, ext := range cert.Extensions {
|
||||
if IdCeSubjectAltName.Equal(ext.Id) {
|
||||
expectedSANs, err := asn1.Marshal([]asn1.RawValue{
|
||||
{Tag: 2, Class: 2, Bytes: []byte(cert.DNSNames[0])},
|
||||
})
|
||||
if err != nil || !bytes.Equal(expectedSANs, ext.Value) {
|
||||
if !bytes.Equal(ext.Value, expectedSANBytes) {
|
||||
return errors.New("SAN extension does not match expected bytes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.EqualFold(cert.DNSNames[0], name.Value) {
|
||||
return errors.New("dNSName does not match expected identifier")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -205,23 +274,19 @@ func checkAcceptableExtensions(exts []pkix.Extension, requiredOIDs []asn1.Object
|
|||
return nil
|
||||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, identifier identifier.ACMEIdentifier, keyAuthorization string) ([]core.ValidationRecord, error) {
|
||||
if identifier.Type != "dns" {
|
||||
va.log.Info(fmt.Sprintf("Identifier type for TLS-ALPN-01 was not DNS: %s", identifier))
|
||||
return nil, berrors.MalformedError("Identifier type for TLS-ALPN-01 was not DNS")
|
||||
func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, ident identifier.ACMEIdentifier, keyAuthorization string) ([]core.ValidationRecord, error) {
|
||||
if ident.Type != identifier.TypeDNS && ident.Type != identifier.TypeIP {
|
||||
va.log.Info(fmt.Sprintf("Identifier type for TLS-ALPN-01 challenge was not DNS or IP: %s", ident))
|
||||
return nil, berrors.MalformedError("Identifier type for TLS-ALPN-01 challenge was not DNS or IP")
|
||||
}
|
||||
|
||||
cert, cs, tvr, problem := va.tryGetChallengeCert(ctx, identifier, &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{ACMETLS1Protocol},
|
||||
ServerName: identifier.Value,
|
||||
})
|
||||
cert, cs, tvr, err := va.tryGetChallengeCert(ctx, ident)
|
||||
// Copy the single validationRecord into the slice that we have to return, and
|
||||
// get a reference to it so we can modify it if we have to.
|
||||
validationRecords := []core.ValidationRecord{tvr}
|
||||
validationRecord := &validationRecords[0]
|
||||
if problem != nil {
|
||||
return validationRecords, problem
|
||||
if err != nil {
|
||||
return validationRecords, err
|
||||
}
|
||||
|
||||
if cs.NegotiatedProtocol != ACMETLS1Protocol {
|
||||
|
@ -237,11 +302,11 @@ func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, identi
|
|||
return berrors.UnauthorizedError(
|
||||
"Incorrect validation certificate for %s challenge. "+
|
||||
"Requested %s from %s. %s",
|
||||
core.ChallengeTypeTLSALPN01, identifier.Value, hostPort, msg)
|
||||
core.ChallengeTypeTLSALPN01, ident.Value, hostPort, msg)
|
||||
}
|
||||
|
||||
// The certificate must be self-signed.
|
||||
err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature)
|
||||
err = cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature)
|
||||
if err != nil || !bytes.Equal(cert.RawSubject, cert.RawIssuer) {
|
||||
return validationRecords, badCertErr(
|
||||
"Received certificate which is not self-signed.")
|
||||
|
@ -259,8 +324,8 @@ func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, identi
|
|||
}
|
||||
|
||||
// The certificate returned must have a subjectAltName extension containing
|
||||
// only the dNSName being validated and no other entries.
|
||||
err = checkExpectedSAN(cert, identifier)
|
||||
// only the identifier being validated and no other entries.
|
||||
err = checkExpectedSAN(cert, ident)
|
||||
if err != nil {
|
||||
names := strings.Join(certAltNames(cert), ", ")
|
||||
return validationRecords, badCertErr(
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
|
@ -49,7 +50,7 @@ var testACMEExt = acmeExtension(IdPeAcmeIdentifier, expectedKeyAuthorization)
|
|||
|
||||
// testTLSCert returns a ready-to-use self-signed certificate with the given
|
||||
// SANs and Extensions. It generates a new ECDSA key on each call.
|
||||
func testTLSCert(names []string, extensions []pkix.Extension) *tls.Certificate {
|
||||
func testTLSCert(names []string, ips []net.IP, extensions []pkix.Extension) *tls.Certificate {
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1337),
|
||||
Subject: pkix.Name{
|
||||
|
@ -63,6 +64,7 @@ func testTLSCert(names []string, extensions []pkix.Extension) *tls.Certificate {
|
|||
BasicConstraintsValid: true,
|
||||
|
||||
DNSNames: names,
|
||||
IPAddresses: ips,
|
||||
ExtraExtensions: extensions,
|
||||
}
|
||||
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
|
@ -77,19 +79,24 @@ func testTLSCert(names []string, extensions []pkix.Extension) *tls.Certificate {
|
|||
// testACMECert returns a certificate with the correctly-formed ACME TLS-ALPN-01
|
||||
// extension with our default test values. Use acmeExtension and testCert if you
|
||||
// need to customize the contents of that extension.
|
||||
func testACMECert(names ...string) *tls.Certificate {
|
||||
return testTLSCert(names, []pkix.Extension{testACMEExt})
|
||||
func testACMECert(names []string) *tls.Certificate {
|
||||
return testTLSCert(names, nil, []pkix.Extension{testACMEExt})
|
||||
}
|
||||
|
||||
// tlsalpn01SrvWithCert creates a test server which will present the given
|
||||
// certificate when asked to do a tls-alpn-01 handshake.
|
||||
func tlsalpn01SrvWithCert(t *testing.T, acmeCert *tls.Certificate, tlsVersion uint16) *httptest.Server {
|
||||
func tlsalpn01SrvWithCert(t *testing.T, acmeCert *tls.Certificate, tlsVersion uint16, ipv6 bool) *httptest.Server {
|
||||
t.Helper()
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
// This is a backstop test for RFC 8738, Section 6. Go's
|
||||
// tls.hostnameInSNI already does the right thing.
|
||||
if net.ParseIP(clientHello.ServerName) != nil {
|
||||
return nil, errors.New("TLS client used a bare IP address for SNI")
|
||||
}
|
||||
return acmeCert, nil
|
||||
},
|
||||
NextProtos: []string{"http/1.1", ACMETLS1Protocol},
|
||||
|
@ -104,6 +111,13 @@ func tlsalpn01SrvWithCert(t *testing.T, acmeCert *tls.Certificate, tlsVersion ui
|
|||
_ = conn.Close()
|
||||
},
|
||||
}
|
||||
if ipv6 {
|
||||
l, err := net.Listen("tcp", "[::1]:0")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err))
|
||||
}
|
||||
hs.Listener = l
|
||||
}
|
||||
hs.StartTLS()
|
||||
return hs
|
||||
}
|
||||
|
@ -112,24 +126,11 @@ func tlsalpn01SrvWithCert(t *testing.T, acmeCert *tls.Certificate, tlsVersion ui
|
|||
// that don't need to customize specific names or extensions in the certificate
|
||||
// served by the TLS server.
|
||||
func testTLSALPN01Srv(t *testing.T) *httptest.Server {
|
||||
return tlsalpn01SrvWithCert(t, testACMECert("expected"), 0)
|
||||
}
|
||||
|
||||
func TestTLSALPN01FailIP(t *testing.T) {
|
||||
hs := testTLSALPN01Srv(t)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("IdentifierType IP shouldn't have worked.")
|
||||
}
|
||||
prob := detailedError(err)
|
||||
test.AssertEquals(t, prob.Type, probs.MalformedProblem)
|
||||
return tlsalpn01SrvWithCert(t, testACMECert([]string{"expected"}), 0, false)
|
||||
}
|
||||
|
||||
func slowTLSSrv() *httptest.Server {
|
||||
cert := testTLSCert([]string{"nomatter"}, nil)
|
||||
cert := testTLSCert([]string{"nomatter"}, nil, nil)
|
||||
server := httptest.NewUnstartedServer(http.DefaultServeMux)
|
||||
server.TLS = &tls.Config{
|
||||
NextProtos: []string{"http/1.1", ACMETLS1Protocol},
|
||||
|
@ -151,7 +152,7 @@ func TestTLSALPNTimeoutAfterConnect(t *testing.T) {
|
|||
defer cancel()
|
||||
|
||||
started := time.Now()
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("slow.server"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("slow.server"), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("Validation should've failed")
|
||||
}
|
||||
|
@ -194,7 +195,7 @@ func TestTLSALPN01DialTimeout(t *testing.T) {
|
|||
// that, just retry until we get something other than "Network unreachable".
|
||||
var err error
|
||||
for range 20 {
|
||||
_, err = va.validateTLSALPN01(ctx, dnsi("unroutable.invalid"), expectedKeyAuthorization)
|
||||
_, err = va.validateTLSALPN01(ctx, identifier.NewDNS("unroutable.invalid"), expectedKeyAuthorization)
|
||||
if err != nil && strings.Contains(err.Error(), "Network unreachable") {
|
||||
continue
|
||||
} else {
|
||||
|
@ -234,7 +235,7 @@ func TestTLSALPN01Refused(t *testing.T) {
|
|||
|
||||
// Take down validation server and check that validation fails.
|
||||
hs.Close()
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("Server's down; expected refusal. Where did we connect?")
|
||||
}
|
||||
|
@ -252,10 +253,10 @@ func TestTLSALPN01TalkingToHTTP(t *testing.T) {
|
|||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
// Make the server only speak HTTP.
|
||||
httpOnly := httpSrv(t, "")
|
||||
httpOnly := httpSrv(t, "", false)
|
||||
va.tlsPort = getPort(httpOnly)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "TLS-SNI-01 validation passed when talking to a HTTP-only server")
|
||||
prob := detailedError(err)
|
||||
expected := "Server only speaks HTTP, not TLS"
|
||||
|
@ -280,7 +281,7 @@ func TestTLSError(t *testing.T) {
|
|||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("TLS validation should have failed: What cert was used?")
|
||||
}
|
||||
|
@ -296,7 +297,7 @@ func TestDNSError(t *testing.T) {
|
|||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("always.invalid"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("always.invalid"), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("TLS validation should have failed: what IP was used?")
|
||||
}
|
||||
|
@ -368,12 +369,44 @@ func TestCertNames(t *testing.T) {
|
|||
test.AssertDeepEquals(t, actual, expected)
|
||||
}
|
||||
|
||||
func TestTLSALPN01Success(t *testing.T) {
|
||||
func TestTLSALPN01SuccessDNS(t *testing.T) {
|
||||
hs := testTLSALPN01Srv(t)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
if err != nil {
|
||||
t.Errorf("Validation failed: %v", err)
|
||||
}
|
||||
test.AssertMetricWithLabelsEquals(
|
||||
t, va.metrics.tlsALPNOIDCounter, prometheus.Labels{"oid": IdPeAcmeIdentifier.String()}, 1)
|
||||
|
||||
hs.Close()
|
||||
}
|
||||
|
||||
func TestTLSALPN01SuccessIPv4(t *testing.T) {
|
||||
cert := testTLSCert(nil, []net.IP{net.ParseIP("127.0.0.1")}, []pkix.Extension{testACMEExt})
|
||||
hs := tlsalpn01SrvWithCert(t, cert, 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedKeyAuthorization)
|
||||
if err != nil {
|
||||
t.Errorf("Validation failed: %v", err)
|
||||
}
|
||||
test.AssertMetricWithLabelsEquals(
|
||||
t, va.metrics.tlsALPNOIDCounter, prometheus.Labels{"oid": IdPeAcmeIdentifier.String()}, 1)
|
||||
|
||||
hs.Close()
|
||||
}
|
||||
|
||||
func TestTLSALPN01SuccessIPv6(t *testing.T) {
|
||||
cert := testTLSCert(nil, []net.IP{net.ParseIP("::1")}, []pkix.Extension{testACMEExt})
|
||||
hs := tlsalpn01SrvWithCert(t, cert, 0, true)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("::1")), expectedKeyAuthorization)
|
||||
if err != nil {
|
||||
t.Errorf("Validation failed: %v", err)
|
||||
}
|
||||
|
@ -393,12 +426,12 @@ func TestTLSALPN01ObsoleteFailure(t *testing.T) {
|
|||
// id-pe OID + 30 (acmeIdentifier) + 1 (v1)
|
||||
IdPeAcmeIdentifierV1Obsolete := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
|
||||
|
||||
cert := testTLSCert([]string{"expected"}, []pkix.Extension{acmeExtension(IdPeAcmeIdentifierV1Obsolete, expectedKeyAuthorization)})
|
||||
hs := tlsalpn01SrvWithCert(t, cert, 0)
|
||||
cert := testTLSCert([]string{"expected"}, nil, []pkix.Extension{acmeExtension(IdPeAcmeIdentifierV1Obsolete, expectedKeyAuthorization)})
|
||||
hs := tlsalpn01SrvWithCert(t, cert, 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
test.AssertNotNil(t, err, "expected validation to fail")
|
||||
test.AssertContains(t, err.Error(), "Required extension OID 1.3.6.1.5.5.7.1.31 is not present")
|
||||
}
|
||||
|
@ -406,12 +439,12 @@ func TestTLSALPN01ObsoleteFailure(t *testing.T) {
|
|||
func TestValidateTLSALPN01BadChallenge(t *testing.T) {
|
||||
badKeyAuthorization := ka("bad token")
|
||||
|
||||
cert := testTLSCert([]string{"expected"}, []pkix.Extension{acmeExtension(IdPeAcmeIdentifier, badKeyAuthorization)})
|
||||
hs := tlsalpn01SrvWithCert(t, cert, 0)
|
||||
cert := testTLSCert([]string{"expected"}, nil, []pkix.Extension{acmeExtension(IdPeAcmeIdentifier, badKeyAuthorization)})
|
||||
hs := tlsalpn01SrvWithCert(t, cert, 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("TLS ALPN validation should have failed.")
|
||||
}
|
||||
|
@ -432,7 +465,7 @@ func TestValidateTLSALPN01BrokenSrv(t *testing.T) {
|
|||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("TLS ALPN validation should have failed.")
|
||||
}
|
||||
|
@ -441,7 +474,7 @@ func TestValidateTLSALPN01BrokenSrv(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestValidateTLSALPN01UnawareSrv(t *testing.T) {
|
||||
cert := testTLSCert([]string{"expected"}, nil)
|
||||
cert := testTLSCert([]string{"expected"}, nil, nil)
|
||||
hs := httptest.NewUnstartedServer(http.DefaultServeMux)
|
||||
hs.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{},
|
||||
|
@ -455,7 +488,7 @@ func TestValidateTLSALPN01UnawareSrv(t *testing.T) {
|
|||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
if err == nil {
|
||||
t.Fatalf("TLS ALPN validation should have failed.")
|
||||
}
|
||||
|
@ -484,11 +517,11 @@ func TestValidateTLSALPN01MalformedExtnValue(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, badExt := range badExtensions {
|
||||
acmeCert := testTLSCert([]string{"expected"}, []pkix.Extension{badExt})
|
||||
hs := tlsalpn01SrvWithCert(t, acmeCert, 0)
|
||||
acmeCert := testTLSCert([]string{"expected"}, nil, []pkix.Extension{badExt})
|
||||
hs := tlsalpn01SrvWithCert(t, acmeCert, 0, false)
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
hs.Close()
|
||||
|
||||
if err == nil {
|
||||
|
@ -504,7 +537,7 @@ func TestValidateTLSALPN01MalformedExtnValue(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTLSALPN01TLSVersion(t *testing.T) {
|
||||
cert := testACMECert("expected")
|
||||
cert := testACMECert([]string{"expected"})
|
||||
|
||||
for _, tc := range []struct {
|
||||
version uint16
|
||||
|
@ -524,11 +557,11 @@ func TestTLSALPN01TLSVersion(t *testing.T) {
|
|||
},
|
||||
} {
|
||||
// Create a server that only negotiates the given TLS version
|
||||
hs := tlsalpn01SrvWithCert(t, cert, tc.version)
|
||||
hs := tlsalpn01SrvWithCert(t, cert, tc.version, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
if !tc.expectError {
|
||||
if err != nil {
|
||||
t.Errorf("expected success, got: %v", err)
|
||||
|
@ -549,24 +582,74 @@ func TestTLSALPN01TLSVersion(t *testing.T) {
|
|||
|
||||
func TestTLSALPN01WrongName(t *testing.T) {
|
||||
// Create a cert with a different name from what we're validating
|
||||
hs := tlsalpn01SrvWithCert(t, testACMECert("incorrect"), 0)
|
||||
hs := tlsalpn01SrvWithCert(t, testACMECert([]string{"incorrect"}), 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
test.AssertContains(t, err.Error(), "dNSName does not match expected identifier")
|
||||
test.AssertContains(t, err.Error(), "identifier does not match expected identifier")
|
||||
}
|
||||
|
||||
func TestTLSALPN01WrongIPv4(t *testing.T) {
|
||||
// Create a cert with a different IP address from what we're validating
|
||||
cert := testTLSCert(nil, []net.IP{net.ParseIP("10.10.10.10")}, []pkix.Extension{testACMEExt})
|
||||
hs := tlsalpn01SrvWithCert(t, cert, 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
test.AssertContains(t, err.Error(), "identifier does not match expected identifier")
|
||||
}
|
||||
|
||||
func TestTLSALPN01WrongIPv6(t *testing.T) {
|
||||
// Create a cert with a different IP address from what we're validating
|
||||
cert := testTLSCert(nil, []net.IP{net.ParseIP("::2")}, []pkix.Extension{testACMEExt})
|
||||
hs := tlsalpn01SrvWithCert(t, cert, 0, true)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("::1")), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
test.AssertContains(t, err.Error(), "identifier does not match expected identifier")
|
||||
}
|
||||
|
||||
func TestTLSALPN01ExtraNames(t *testing.T) {
|
||||
// Create a cert with two names when we only want to validate one.
|
||||
hs := tlsalpn01SrvWithCert(t, testACMECert("expected", "extra"), 0)
|
||||
hs := tlsalpn01SrvWithCert(t, testACMECert([]string{"expected", "extra"}), 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
test.AssertContains(t, err.Error(), "wrong number of dNSNames")
|
||||
test.AssertContains(t, err.Error(), "wrong number of identifiers")
|
||||
}
|
||||
|
||||
func TestTLSALPN01WrongIdentType(t *testing.T) {
|
||||
// Create a cert with an IP address encoded as a name.
|
||||
hs := tlsalpn01SrvWithCert(t, testACMECert([]string{"127.0.0.1"}), 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
test.AssertContains(t, err.Error(), "wrong number of identifiers")
|
||||
}
|
||||
|
||||
func TestTLSALPN01TooManyIdentTypes(t *testing.T) {
|
||||
// Create a cert with both a name and an IP address when we only want to validate one.
|
||||
hs := tlsalpn01SrvWithCert(t, testTLSCert([]string{"expected"}, []net.IP{net.ParseIP("127.0.0.1")}, []pkix.Extension{testACMEExt}), 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
test.AssertContains(t, err.Error(), "wrong number of identifiers")
|
||||
|
||||
_, err = va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
test.AssertContains(t, err.Error(), "wrong number of identifiers")
|
||||
}
|
||||
|
||||
func TestTLSALPN01NotSelfSigned(t *testing.T) {
|
||||
|
@ -614,11 +697,11 @@ func TestTLSALPN01NotSelfSigned(t *testing.T) {
|
|||
PrivateKey: eeKey,
|
||||
}
|
||||
|
||||
hs := tlsalpn01SrvWithCert(t, acmeCert, 0)
|
||||
hs := tlsalpn01SrvWithCert(t, acmeCert, 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err = va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err = va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
test.AssertContains(t, err.Error(), "not self-signed")
|
||||
|
||||
|
@ -632,11 +715,11 @@ func TestTLSALPN01NotSelfSigned(t *testing.T) {
|
|||
PrivateKey: eeKey,
|
||||
}
|
||||
|
||||
hs = tlsalpn01SrvWithCert(t, acmeCert, 0)
|
||||
hs = tlsalpn01SrvWithCert(t, acmeCert, 0, false)
|
||||
|
||||
va, _ = setup(hs, "", nil, nil)
|
||||
|
||||
_, err = va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err = va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
test.AssertContains(t, err.Error(), "not self-signed")
|
||||
}
|
||||
|
@ -671,11 +754,11 @@ func TestTLSALPN01ExtraIdentifiers(t *testing.T) {
|
|||
PrivateKey: key,
|
||||
}
|
||||
|
||||
hs := tlsalpn01SrvWithCert(t, acmeCert, tls.VersionTLS12)
|
||||
hs := tlsalpn01SrvWithCert(t, acmeCert, tls.VersionTLS12, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err = va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err = va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
test.AssertContains(t, err.Error(), "Received certificate with unexpected identifiers")
|
||||
}
|
||||
|
@ -694,11 +777,11 @@ func TestTLSALPN01ExtraSANs(t *testing.T) {
|
|||
}
|
||||
|
||||
extensions := []pkix.Extension{testACMEExt, subjectAltName, subjectAltName}
|
||||
hs := tlsalpn01SrvWithCert(t, testTLSCert([]string{"expected"}, extensions), 0)
|
||||
hs := tlsalpn01SrvWithCert(t, testTLSCert([]string{"expected"}, nil, extensions), 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err = va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err = va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
// In go >= 1.19, the TLS client library detects that the certificate has
|
||||
// a duplicate extension and terminates the connection itself.
|
||||
|
@ -709,11 +792,11 @@ func TestTLSALPN01ExtraSANs(t *testing.T) {
|
|||
func TestTLSALPN01ExtraAcmeExtensions(t *testing.T) {
|
||||
// Create a cert with multiple SAN extensions
|
||||
extensions := []pkix.Extension{testACMEExt, testACMEExt}
|
||||
hs := tlsalpn01SrvWithCert(t, testTLSCert([]string{"expected"}, extensions), 0)
|
||||
hs := tlsalpn01SrvWithCert(t, testTLSCert([]string{"expected"}, nil, extensions), 0, false)
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, dnsi("expected"), expectedKeyAuthorization)
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "validation should have failed")
|
||||
// In go >= 1.19, the TLS client library detects that the certificate has
|
||||
// a duplicate extension and terminates the connection itself.
|
||||
|
@ -770,3 +853,15 @@ func TestAcceptableExtensions(t *testing.T) {
|
|||
err = checkAcceptableExtensions(okayWithUnexpectedExt, requireAcmeAndSAN)
|
||||
test.AssertNotError(t, err, "Correct type and number of extensions")
|
||||
}
|
||||
|
||||
func TestTLSALPN01BadIdentifier(t *testing.T) {
|
||||
hs := httpSrv(t, expectedToken, false)
|
||||
defer hs.Close()
|
||||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.ACMEIdentifier{Type: "smime", Value: "dobber@bad.horse"}, expectedKeyAuthorization)
|
||||
test.AssertError(t, err, "Server accepted a hypothetical S/MIME identifier")
|
||||
prob := detailedError(err)
|
||||
test.AssertContains(t, prob.Error(), "Identifier type for TLS-ALPN-01 challenge was not DNS or IP")
|
||||
}
|
||||
|
|
23
va/va.go
23
va/va.go
|
@ -216,6 +216,7 @@ type ValidationAuthorityImpl struct {
|
|||
singleDialTimeout time.Duration
|
||||
perspective string
|
||||
rir string
|
||||
isReservedIPFunc func(ip net.IP) bool
|
||||
|
||||
metrics *vaMetrics
|
||||
}
|
||||
|
@ -235,6 +236,7 @@ func NewValidationAuthorityImpl(
|
|||
accountURIPrefixes []string,
|
||||
perspective string,
|
||||
rir string,
|
||||
reservedIPChecker func(ip net.IP) bool,
|
||||
) (*ValidationAuthorityImpl, error) {
|
||||
|
||||
if len(accountURIPrefixes) == 0 {
|
||||
|
@ -271,6 +273,7 @@ func NewValidationAuthorityImpl(
|
|||
singleDialTimeout: 10 * time.Second,
|
||||
perspective: perspective,
|
||||
rir: rir,
|
||||
isReservedIPFunc: reservedIPChecker,
|
||||
}
|
||||
|
||||
return va, nil
|
||||
|
@ -410,13 +413,12 @@ func (va *ValidationAuthorityImpl) validateChallenge(
|
|||
token string,
|
||||
keyAuthorization string,
|
||||
) ([]core.ValidationRecord, error) {
|
||||
// Strip a (potential) leading wildcard token from the identifier.
|
||||
ident.Value = strings.TrimPrefix(ident.Value, "*.")
|
||||
|
||||
switch kind {
|
||||
case core.ChallengeTypeHTTP01:
|
||||
return va.validateHTTP01(ctx, ident, token, keyAuthorization)
|
||||
case core.ChallengeTypeDNS01:
|
||||
// Strip a (potential) leading wildcard token from the identifier.
|
||||
ident.Value = strings.TrimPrefix(ident.Value, "*.")
|
||||
return va.validateDNS01(ctx, ident, keyAuthorization)
|
||||
case core.ChallengeTypeTLSALPN01:
|
||||
return va.validateTLSALPN01(ctx, ident, keyAuthorization)
|
||||
|
@ -640,7 +642,7 @@ func (va *ValidationAuthorityImpl) doRemoteOperation(ctx context.Context, op rem
|
|||
type validationLogEvent struct {
|
||||
AuthzID string
|
||||
Requester int64
|
||||
Identifier string
|
||||
Identifier identifier.ACMEIdentifier
|
||||
Challenge core.Challenge
|
||||
Error string `json:",omitempty"`
|
||||
InternalError string `json:",omitempty"`
|
||||
|
@ -659,7 +661,14 @@ type validationLogEvent struct {
|
|||
// implements the DCV portion of Multi-Perspective Issuance Corroboration as
|
||||
// defined in BRs Sections 3.2.2.9 and 5.4.1.
|
||||
func (va *ValidationAuthorityImpl) DoDCV(ctx context.Context, req *vapb.PerformValidationRequest) (*vapb.ValidationResult, error) {
|
||||
if core.IsAnyNilOrZero(req, req.DnsName, req.Challenge, req.Authz, req.ExpectedKeyAuthorization) {
|
||||
// TODO(#8023): Once DnsNames are no longer used in RPCs, use req.Identifier
|
||||
// directly instead of setting ident.
|
||||
if req.Identifier != nil && req.DnsName != "" {
|
||||
return nil, errors.New("both Identifier and DNSName are set")
|
||||
}
|
||||
ident := identifier.FromProtoWithDefault(req.Identifier, req.DnsName)
|
||||
|
||||
if core.IsAnyNilOrZero(req, ident, req.Challenge, req.Authz, req.ExpectedKeyAuthorization) {
|
||||
return nil, berrors.InternalServerError("Incomplete validation request")
|
||||
}
|
||||
|
||||
|
@ -683,7 +692,7 @@ func (va *ValidationAuthorityImpl) DoDCV(ctx context.Context, req *vapb.PerformV
|
|||
logEvent := validationLogEvent{
|
||||
AuthzID: req.Authz.Id,
|
||||
Requester: req.Authz.RegID,
|
||||
Identifier: req.DnsName,
|
||||
Identifier: ident,
|
||||
Challenge: chall,
|
||||
}
|
||||
defer func() {
|
||||
|
@ -717,7 +726,7 @@ func (va *ValidationAuthorityImpl) DoDCV(ctx context.Context, req *vapb.PerformV
|
|||
// was successful or not, and cannot themselves fail.
|
||||
records, err := va.validateChallenge(
|
||||
ctx,
|
||||
identifier.NewDNS(req.DnsName),
|
||||
ident,
|
||||
chall.Type,
|
||||
chall.Token,
|
||||
req.ExpectedKeyAuthorization,
|
||||
|
|
111
va/va_test.go
111
va/va_test.go
|
@ -10,6 +10,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -69,11 +70,6 @@ var expectedToken = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
|
|||
var expectedThumbprint = "9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"
|
||||
var expectedKeyAuthorization = ka(expectedToken)
|
||||
|
||||
// Return an ACME DNS identifier for the given hostname
|
||||
func dnsi(hostname string) identifier.ACMEIdentifier {
|
||||
return identifier.NewDNS(hostname)
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -86,9 +82,9 @@ func TestMain(m *testing.M) {
|
|||
|
||||
var accountURIPrefixes = []string{"http://boulder.service.consul:4000/acme/reg/"}
|
||||
|
||||
func createValidationRequest(domain string, challengeType core.AcmeChallenge) *vapb.PerformValidationRequest {
|
||||
func createValidationRequest(ident identifier.ACMEIdentifier, challengeType core.AcmeChallenge) *vapb.PerformValidationRequest {
|
||||
return &vapb.PerformValidationRequest{
|
||||
DnsName: domain,
|
||||
Identifier: ident.AsProto(),
|
||||
Challenge: &corepb.Challenge{
|
||||
Type: string(challengeType),
|
||||
Status: string(core.StatusPending),
|
||||
|
@ -103,6 +99,21 @@ func createValidationRequest(domain string, challengeType core.AcmeChallenge) *v
|
|||
}
|
||||
}
|
||||
|
||||
// isNonLoopbackReservedIP is a mock reserved IP checker that permits loopback
|
||||
// networks.
|
||||
func isNonLoopbackReservedIP(ip net.IP) bool {
|
||||
loopbackV4 := netip.MustParsePrefix("127.0.0.0/8")
|
||||
loopbackV6 := netip.MustParsePrefix("::1/128")
|
||||
netIPAddr, ok := netip.AddrFromSlice(ip)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("error parsing IP (%s)", ip))
|
||||
}
|
||||
if loopbackV4.Contains(netIPAddr) || loopbackV6.Contains(netIPAddr) {
|
||||
return false
|
||||
}
|
||||
return bdns.IsReservedIP(ip)
|
||||
}
|
||||
|
||||
// setup returns an in-memory VA and a mock logger. The default resolver client
|
||||
// is MockClient{}, but can be overridden.
|
||||
//
|
||||
|
@ -136,6 +147,7 @@ func setup(srv *httptest.Server, userAgent string, remoteVAs []RemoteVA, mockDNS
|
|||
accountURIPrefixes,
|
||||
perspective,
|
||||
"",
|
||||
isNonLoopbackReservedIP,
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to create validation authority: %v", err))
|
||||
|
@ -321,6 +333,7 @@ func TestNewValidationAuthorityImplWithDuplicateRemotes(t *testing.T) {
|
|||
accountURIPrefixes,
|
||||
"example perspective",
|
||||
"",
|
||||
isNonLoopbackReservedIP,
|
||||
)
|
||||
test.AssertError(t, err, "NewValidationAuthorityImpl allowed duplicate remote perspectives")
|
||||
test.AssertContains(t, err.Error(), "duplicate remote VA perspective \"dadaist\"")
|
||||
|
@ -343,7 +356,7 @@ func TestPerformValidationWithMismatchedRemoteVAPerspectives(t *testing.T) {
|
|||
remoteVAs = append(remoteVAs, mismatched1, mismatched2)
|
||||
|
||||
va, mockLog := setup(nil, "", remoteVAs, nil)
|
||||
req := createValidationRequest("good-dns01.com", core.ChallengeTypeDNS01)
|
||||
req := createValidationRequest(identifier.NewDNS("good-dns01.com"), core.ChallengeTypeDNS01)
|
||||
res, _ := va.DoDCV(context.Background(), req)
|
||||
test.AssertNotNil(t, res.GetProblem(), "validation succeeded with mismatched remote VA perspectives")
|
||||
test.AssertEquals(t, len(mockLog.GetAllMatching("Expected perspective")), 2)
|
||||
|
@ -366,7 +379,7 @@ func TestPerformValidationWithMismatchedRemoteVARIRs(t *testing.T) {
|
|||
remoteVAs = append(remoteVAs, mismatched1, mismatched2)
|
||||
|
||||
va, mockLog := setup(nil, "", remoteVAs, nil)
|
||||
req := createValidationRequest("good-dns01.com", core.ChallengeTypeDNS01)
|
||||
req := createValidationRequest(identifier.NewDNS("good-dns01.com"), core.ChallengeTypeDNS01)
|
||||
res, _ := va.DoDCV(context.Background(), req)
|
||||
test.AssertNotNil(t, res.GetProblem(), "validation succeeded with mismatched remote VA perspectives")
|
||||
test.AssertEquals(t, len(mockLog.GetAllMatching("Expected perspective")), 2)
|
||||
|
@ -375,7 +388,7 @@ func TestPerformValidationWithMismatchedRemoteVARIRs(t *testing.T) {
|
|||
func TestValidateMalformedChallenge(t *testing.T) {
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
|
||||
_, err := va.validateChallenge(ctx, dnsi("example.com"), "fake-type-01", expectedToken, expectedKeyAuthorization)
|
||||
_, err := va.validateChallenge(ctx, identifier.NewDNS("example.com"), "fake-type-01", expectedToken, expectedKeyAuthorization)
|
||||
|
||||
prob := detailedError(err)
|
||||
test.AssertEquals(t, prob.Type, probs.MalformedProblem)
|
||||
|
@ -385,7 +398,7 @@ func TestPerformValidationInvalid(t *testing.T) {
|
|||
t.Parallel()
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
|
||||
req := createValidationRequest("foo.com", core.ChallengeTypeDNS01)
|
||||
req := createValidationRequest(identifier.NewDNS("foo.com"), core.ChallengeTypeDNS01)
|
||||
res, _ := va.DoDCV(context.Background(), req)
|
||||
test.Assert(t, res.Problem != nil, "validation succeeded")
|
||||
test.AssertMetricWithLabelsEquals(t, va.metrics.validationLatency, prometheus.Labels{
|
||||
|
@ -404,7 +417,7 @@ func TestInternalErrorLogged(t *testing.T) {
|
|||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
|
||||
defer cancel()
|
||||
req := createValidationRequest("nonexistent.com", core.ChallengeTypeHTTP01)
|
||||
req := createValidationRequest(identifier.NewDNS("nonexistent.com"), core.ChallengeTypeHTTP01)
|
||||
_, err := va.DoDCV(ctx, req)
|
||||
test.AssertNotError(t, err, "failed validation should not be an error")
|
||||
matchingLogs := mockLog.GetAllMatching(
|
||||
|
@ -418,7 +431,7 @@ func TestPerformValidationValid(t *testing.T) {
|
|||
va, mockLog := setup(nil, "", nil, nil)
|
||||
|
||||
// create a challenge with well known token
|
||||
req := createValidationRequest("good-dns01.com", core.ChallengeTypeDNS01)
|
||||
req := createValidationRequest(identifier.NewDNS("good-dns01.com"), core.ChallengeTypeDNS01)
|
||||
res, _ := va.DoDCV(context.Background(), req)
|
||||
test.Assert(t, res.Problem == nil, fmt.Sprintf("validation failed: %#v", res.Problem))
|
||||
test.AssertMetricWithLabelsEquals(t, va.metrics.validationLatency, prometheus.Labels{
|
||||
|
@ -432,7 +445,8 @@ func TestPerformValidationValid(t *testing.T) {
|
|||
if len(resultLog) != 1 {
|
||||
t.Fatalf("Wrong number of matching lines for 'Validation result'")
|
||||
}
|
||||
if !strings.Contains(resultLog[0], `"Identifier":"good-dns01.com"`) {
|
||||
|
||||
if !strings.Contains(resultLog[0], `"Identifier":{"type":"dns","value":"good-dns01.com"}`) {
|
||||
t.Error("PerformValidation didn't log validation identifier.")
|
||||
}
|
||||
}
|
||||
|
@ -445,7 +459,7 @@ func TestPerformValidationWildcard(t *testing.T) {
|
|||
va, mockLog := setup(nil, "", nil, nil)
|
||||
|
||||
// create a challenge with well known token
|
||||
req := createValidationRequest("*.good-dns01.com", core.ChallengeTypeDNS01)
|
||||
req := createValidationRequest(identifier.NewDNS("*.good-dns01.com"), core.ChallengeTypeDNS01)
|
||||
// perform a validation for a wildcard name
|
||||
res, _ := va.DoDCV(context.Background(), req)
|
||||
test.Assert(t, res.Problem == nil, fmt.Sprintf("validation failed: %#v", res.Problem))
|
||||
|
@ -462,7 +476,7 @@ func TestPerformValidationWildcard(t *testing.T) {
|
|||
}
|
||||
|
||||
// We expect that the top level Identifier reflect the wildcard name
|
||||
if !strings.Contains(resultLog[0], `"Identifier":"*.good-dns01.com"`) {
|
||||
if !strings.Contains(resultLog[0], `"Identifier":{"type":"dns","value":"*.good-dns01.com"}`) {
|
||||
t.Errorf("PerformValidation didn't log correct validation identifier.")
|
||||
}
|
||||
// We expect that the ValidationRecord contain the correct non-wildcard
|
||||
|
@ -476,7 +490,7 @@ func TestMultiVA(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
// Create a new challenge to use for the httpSrv
|
||||
req := createValidationRequest("localhost", core.ChallengeTypeHTTP01)
|
||||
req := createValidationRequest(identifier.NewDNS("localhost"), core.ChallengeTypeHTTP01)
|
||||
|
||||
brokenVA := RemoteClients{
|
||||
VAClient: brokenRemoteVA{},
|
||||
|
@ -724,7 +738,7 @@ func TestMultiVAEarlyReturn(t *testing.T) {
|
|||
|
||||
// Perform all validations
|
||||
start := time.Now()
|
||||
req := createValidationRequest("localhost", core.ChallengeTypeHTTP01)
|
||||
req := createValidationRequest(identifier.NewDNS("localhost"), core.ChallengeTypeHTTP01)
|
||||
res, _ := localVA.DoDCV(ctx, req)
|
||||
|
||||
// It should always fail
|
||||
|
@ -763,13 +777,12 @@ func TestMultiVAPolicy(t *testing.T) {
|
|||
localVA, _ := setupWithRemotes(ms.Server, pass, remoteConfs, nil)
|
||||
|
||||
// Perform validation for a domain not in the disabledDomains list
|
||||
req := createValidationRequest("letsencrypt.org", core.ChallengeTypeHTTP01)
|
||||
req := createValidationRequest(identifier.NewDNS("letsencrypt.org"), core.ChallengeTypeHTTP01)
|
||||
res, _ := localVA.DoDCV(ctx, req)
|
||||
// It should fail
|
||||
if res.Problem == nil {
|
||||
t.Error("expected prob from PerformValidation, got nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMultiVALogging(t *testing.T) {
|
||||
|
@ -785,7 +798,7 @@ func TestMultiVALogging(t *testing.T) {
|
|||
defer ms.Close()
|
||||
|
||||
va, _ := setupWithRemotes(ms.Server, pass, remoteConfs, nil)
|
||||
req := createValidationRequest("letsencrypt.org", core.ChallengeTypeHTTP01)
|
||||
req := createValidationRequest(identifier.NewDNS("letsencrypt.org"), core.ChallengeTypeHTTP01)
|
||||
res, err := va.DoDCV(ctx, req)
|
||||
test.Assert(t, res.Problem == nil, fmt.Sprintf("validation failed with: %#v", res.Problem))
|
||||
test.AssertNotError(t, err, "performing validation")
|
||||
|
@ -842,3 +855,59 @@ func TestDetailedError(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPerformValidationDnsName modifies the PerformValidationRequest to test
|
||||
// backward compatibility during the transition to using an Identifier instead
|
||||
// of a DnsName.
|
||||
//
|
||||
// TODO(#8023): Remove this after the transition is over.
|
||||
func TestPerformValidationDnsName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
identDomain string
|
||||
transmogrifier func(*vapb.PerformValidationRequest)
|
||||
expectErr bool
|
||||
expectErrString string
|
||||
expectLog string
|
||||
}{
|
||||
{
|
||||
name: "Both Identifier and DnsName",
|
||||
identDomain: "good-dns01.com",
|
||||
transmogrifier: func(req *vapb.PerformValidationRequest) {
|
||||
req.DnsName = "good-dns02.com"
|
||||
},
|
||||
expectErr: true,
|
||||
expectErrString: "both Identifier and DNSName are set",
|
||||
expectLog: `"Identifier":{"type":"dns","value":"good-dns01.com"}`,
|
||||
},
|
||||
{
|
||||
name: "No Identifier",
|
||||
identDomain: "good-dns01.com",
|
||||
transmogrifier: func(req *vapb.PerformValidationRequest) {
|
||||
req.DnsName = "good-dns02.com"
|
||||
req.Identifier = nil
|
||||
},
|
||||
expectLog: `"Identifier":{"type":"dns","value":"good-dns02.com"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
va, _ := setup(nil, "", nil, nil)
|
||||
|
||||
// create a challenge with well known token
|
||||
req := createValidationRequest(identifier.NewDNS(tc.identDomain), core.ChallengeTypeDNS01)
|
||||
tc.transmogrifier(req)
|
||||
res, err := va.DoDCV(context.Background(), req)
|
||||
if tc.expectErr {
|
||||
test.AssertDeepEquals(t, err, errors.New(tc.expectErrString))
|
||||
} else {
|
||||
test.AssertNotNil(t, res.GetProblem(), fmt.Sprintf("validation failed: %#v", res.Problem))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue