diff --git a/acme/challenge.go b/acme/challenge.go index 90960fc4..d541bed2 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -107,23 +107,30 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb } func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo *ValidateChallengeOptions) error { + + var serverName string + + // RFC8738 states that, if HostName is IP, it should be the ARPA + // address https://datatracker.ietf.org/doc/html/rfc8738#section-6. + // It also references TLS Extensions [RFC6066]. + if ip := net.ParseIP(ch.Value); ip != nil { + serverName = reverseAddr(ip) + } else { + serverName = ch.Value + } + config := &tls.Config{ NextProtos: []string{"acme-tls/1"}, // https://tools.ietf.org/html/rfc8737#section-4 // ACME servers that implement "acme-tls/1" MUST only negotiate TLS 1.2 // [RFC5246] or higher when connecting to clients for validation. MinVersion: tls.VersionTLS12, - ServerName: ch.Value, + ServerName: serverName, InsecureSkipVerify: true, // we expect a self-signed challenge certificate } hostPort := net.JoinHostPort(ch.Value, "443") - fmt.Println(hostPort) - fmt.Println(fmt.Sprintf("%#+v", config)) - - time.Sleep(5 * time.Second) // TODO: remove this; client seems to take a while to start serving; the server does not seem to retry the conn - conn, err := vo.TLSDial("tcp", hostPort, config) if err != nil { fmt.Println(err) @@ -135,9 +142,6 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON cs := conn.ConnectionState() certs := cs.PeerCertificates - fmt.Println(fmt.Sprintf("%#+v", cs)) - fmt.Println(fmt.Sprintf("%#+v", certs)) - if len(certs) == 0 { return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "%s challenge for %s resulted in no certificates", ch.Type, ch.Value)) @@ -149,7 +153,6 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON } leafCert := certs[0] - fmt.Println(fmt.Sprintf("%#+v", leafCert)) // if no DNS names present, look for IP address and verify that exactly one exists if len(leafCert.DNSNames) == 0 { @@ -262,6 +265,50 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK return nil } +// reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP +// address addr suitable for rDNS (PTR) record lookup or an error if it fails +// to parse the IP address. +// Implementation taken and adapted from https://golang.org/src/net/dnsclient.go?s=780:834#L20 +func reverseAddr(ip net.IP) (arpa string) { + if ip.To4() != nil { + return uitoa(uint(ip[15])) + "." + uitoa(uint(ip[14])) + "." + uitoa(uint(ip[13])) + "." + uitoa(uint(ip[12])) + ".in-addr.arpa." + } + // Must be IPv6 + buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) + // Add it, in reverse, to the buffer + for i := len(ip) - 1; i >= 0; i-- { + v := ip[i] + buf = append(buf, hexit[v&0xF], + '.', + hexit[v>>4], + '.') + } + // Append "ip6.arpa." and return (buf already has the final .) + buf = append(buf, "ip6.arpa."...) + return string(buf) +} + +// Convert unsigned integer to decimal string. +// Implementation taken from https://golang.org/src/net/parse.go +func uitoa(val uint) string { + if val == 0 { // avoid string allocation + return "0" + } + var buf [20]byte // big enough for 64bit value base 10 + i := len(buf) - 1 + for val >= 10 { + q := val / 10 + buf[i] = byte('0' + val - q*10) + i-- + val = q + } + // val < 10 + buf[i] = byte('0' + val) + return string(buf[i:]) +} + +const hexit = "0123456789abcdef" + // KeyAuthorization creates the ACME key authorization value from a token // and a jwk. func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) {