diff --git a/acme/challenge.go b/acme/challenge.go index 9f3ca388..beda1bf5 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -157,15 +157,40 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb return nil } +// rootedName adds a trailing "." to a given domain name. +func rootedName(name string) string { + if name == "" || name[len(name)-1] != '.' { + return name + "." + } + return name +} + // http01ChallengeHost checks if a Challenge value is an IPv6 address // and adds square brackets if that's the case, so that it can be used // as a hostname. Returns the original Challenge value as the host to // use in other cases. func http01ChallengeHost(value string) string { - if ip := net.ParseIP(value); ip != nil && ip.To4() == nil { - value = "[" + value + "]" + if ip := net.ParseIP(value); ip != nil { + if ip.To4() == nil { + value = "[" + value + "]" + } + return value } - return value + return rootedName(value) +} + +// tlsAlpn01ChallengeHost returns the rooted DNS used on TLS-ALPN-01 +// validations. +func tlsAlpn01ChallengeHost(name string) string { + if ip := net.ParseIP(name); ip != nil { + return name + } + return rootedName(name) +} + +// dns01ChallengeHost returns the TXT record used in DNS-01 validations. +func dns01ChallengeHost(domain string) string { + return "_acme-challenge." + rootedName(domain) } func tlsAlert(err error) uint8 { @@ -190,13 +215,12 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON InsecureSkipVerify: true, //nolint:gosec // we expect a self-signed challenge certificate } - var hostPort string - // Allow to change TLS port for testing purposes. + hostPort := tlsAlpn01ChallengeHost(ch.Value) if port := InsecurePortTLSALPN01; port == 0 { - hostPort = net.JoinHostPort(ch.Value, "443") + hostPort = net.JoinHostPort(hostPort, "443") } else { - hostPort = net.JoinHostPort(ch.Value, strconv.Itoa(port)) + hostPort = net.JoinHostPort(hostPort, strconv.Itoa(port)) } vc := MustClientFromContext(ctx) @@ -211,7 +235,7 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON "cannot negotiate ALPN acme-tls/1 protocol for tls-alpn-01 challenge")) } return storeError(ctx, db, ch, false, WrapError(ErrorConnectionType, err, - "error doing TLS dial for %s", hostPort)) + "error doing TLS dial for %s", ch.Value)) } defer conn.Close() @@ -307,7 +331,7 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK domain := strings.TrimPrefix(ch.Value, "*.") vc := MustClientFromContext(ctx) - txtRecords, err := vc.LookupTxt("_acme-challenge." + domain) + txtRecords, err := vc.LookupTxt(dns01ChallengeHost(domain)) if err != nil { return storeError(ctx, db, ch, false, WrapError(ErrorDNSType, err, "error looking up TXT records for domain %s", domain)) @@ -1058,14 +1082,10 @@ func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, // should be the ARPA address https://datatracker.ietf.org/doc/html/rfc8738#section-6. // It also references TLS Extensions [RFC6066]. func serverName(ch *Challenge) string { - var serverName string - ip := net.ParseIP(ch.Value) - if ip != nil { - serverName = reverseAddr(ip) - } else { - serverName = ch.Value + if ip := net.ParseIP(ch.Value); ip != nil { + return reverseAddr(ip) } - return serverName + return ch.Value } // reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 5cede1c5..61264b55 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -457,7 +457,7 @@ func TestChallenge_Validate(t *testing.T) { assert.Equal(t, "zap.internal", updch.Value) assert.Equal(t, StatusPending, updch.Status) - err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token) + err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s: force", ch.Token) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) assert.Equal(t, err.Detail, updch.Error.Detail) @@ -494,7 +494,7 @@ func TestChallenge_Validate(t *testing.T) { assert.Equal(t, "zap.internal", updch.Value) assert.Equal(t, StatusPending, updch.Status) - err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token) + err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s: force", ch.Token) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) assert.Equal(t, err.Detail, updch.Error.Detail) @@ -536,7 +536,7 @@ func TestChallenge_Validate(t *testing.T) { assert.Equal(t, "zap.internal", updch.Value) assert.Equal(t, StatusPending, updch.Status) - err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal:8080/.well-known/acme-challenge/%s: force", ch.Token) + err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal.:8080/.well-known/acme-challenge/%s: force", ch.Token) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) assert.Equal(t, err.Detail, updch.Error.Detail) @@ -646,7 +646,7 @@ func TestChallenge_Validate(t *testing.T) { assert.Equal(t, "zap.internal", updch.Value) assert.Equal(t, StatusPending, updch.Status) - err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: force", ch.Value) + err := NewError(ErrorConnectionType, "error doing TLS dial for %v: force", ch.Value) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) @@ -926,7 +926,7 @@ func TestHTTP01Validate(t *testing.T) { assert.Equal(t, "zap.internal", updch.Value) assert.Equal(t, StatusPending, updch.Status) - err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token) + err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s: force", ch.Token) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) assert.Equal(t, err.Detail, updch.Error.Detail) @@ -961,7 +961,7 @@ func TestHTTP01Validate(t *testing.T) { assert.Equal(t, "zap.internal", updch.Value) assert.Equal(t, StatusPending, updch.Status) - err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token) + err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s: force", ch.Token) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) assert.Equal(t, err.Detail, updch.Error.Detail) @@ -998,7 +998,7 @@ func TestHTTP01Validate(t *testing.T) { assert.Equal(t, "zap.internal", updch.Value) assert.Equal(t, StatusPending, updch.Status) - err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s with status code 400", ch.Token) + err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s with status code 400", ch.Token) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) assert.Equal(t, err.Detail, updch.Error.Detail) @@ -1036,7 +1036,7 @@ func TestHTTP01Validate(t *testing.T) { assert.Equal(t, "zap.internal", updch.Value) assert.Equal(t, StatusPending, updch.Status) - err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s with status code 400", ch.Token) + err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s with status code 400", ch.Token) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) assert.Equal(t, err.Detail, updch.Error.Detail) @@ -1065,7 +1065,7 @@ func TestHTTP01Validate(t *testing.T) { }, nil }, }, - err: NewErrorISE("error reading response body for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token), + err: NewErrorISE("error reading response body for url http://zap.internal./.well-known/acme-challenge/%s: force", ch.Token), } }, "fail/key-auth-gen-error": func(t *testing.T) test { @@ -1723,7 +1723,7 @@ func TestTLSALPN01Validate(t *testing.T) { assert.Equal(t, ChallengeType("tls-alpn-01"), updch.Type) assert.Equal(t, "zap.internal", updch.Value) - err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: force", ch.Value) + err := NewError(ErrorConnectionType, "error doing TLS dial for %v: force", ch.Value) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) @@ -1754,7 +1754,7 @@ func TestTLSALPN01Validate(t *testing.T) { assert.Equal(t, ChallengeType("tls-alpn-01"), updch.Type) assert.Equal(t, "zap.internal", updch.Value) - err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: force", ch.Value) + err := NewError(ErrorConnectionType, "error doing TLS dial for %v: force", ch.Value) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) @@ -1786,7 +1786,7 @@ func TestTLSALPN01Validate(t *testing.T) { assert.Equal(t, ChallengeType("tls-alpn-01"), updch.Type) assert.Equal(t, "zap.internal", updch.Value) - err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: context deadline exceeded", ch.Value) + err := NewError(ErrorConnectionType, "error doing TLS dial for %v: context deadline exceeded", ch.Value) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) @@ -2774,7 +2774,12 @@ func Test_http01ChallengeHost(t *testing.T) { { name: "dns", value: "www.example.com", - want: "www.example.com", + want: "www.example.com.", + }, + { + name: "rooted dns", + value: "www.example.com.", + want: "www.example.com.", }, { name: "ipv4", @@ -4301,3 +4306,43 @@ func createSubjectAltNameExtension(dnsNames, emailAddresses x509util.MultiString Value: rawBytes, }, nil } + +func Test_tlsAlpn01ChallengeHost(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want string + }{ + {"dns", args{"smallstep.com"}, "smallstep.com."}, + {"rooted dns", args{"smallstep.com."}, "smallstep.com."}, + {"ipv4", args{"1.2.3.4"}, "1.2.3.4"}, + {"ipv6", args{"2607:f8b0:4023:1009::71"}, "2607:f8b0:4023:1009::71"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tlsAlpn01ChallengeHost(tt.args.name)) + }) + } +} + +func Test_dns01ChallengeHost(t *testing.T) { + type args struct { + domain string + } + tests := []struct { + name string + args args + want string + }{ + {"dns", args{"smallstep.com"}, "_acme-challenge.smallstep.com."}, + {"rooted dns", args{"smallstep.com."}, "_acme-challenge.smallstep.com."}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, dns01ChallengeHost(tt.args.domain)) + }) + } +}