Add support for TLS-ALPN-01 challenge

This commit is contained in:
Herman Slatman 2021-06-04 00:01:43 +02:00
parent 76dcf542d4
commit 2f40011da8
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F

View file

@ -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 { 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{ config := &tls.Config{
NextProtos: []string{"acme-tls/1"}, NextProtos: []string{"acme-tls/1"},
// https://tools.ietf.org/html/rfc8737#section-4 // https://tools.ietf.org/html/rfc8737#section-4
// ACME servers that implement "acme-tls/1" MUST only negotiate TLS 1.2 // ACME servers that implement "acme-tls/1" MUST only negotiate TLS 1.2
// [RFC5246] or higher when connecting to clients for validation. // [RFC5246] or higher when connecting to clients for validation.
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
ServerName: ch.Value, ServerName: serverName,
InsecureSkipVerify: true, // we expect a self-signed challenge certificate InsecureSkipVerify: true, // we expect a self-signed challenge certificate
} }
hostPort := net.JoinHostPort(ch.Value, "443") 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) conn, err := vo.TLSDial("tcp", hostPort, config)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -135,9 +142,6 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
cs := conn.ConnectionState() cs := conn.ConnectionState()
certs := cs.PeerCertificates certs := cs.PeerCertificates
fmt.Println(fmt.Sprintf("%#+v", cs))
fmt.Println(fmt.Sprintf("%#+v", certs))
if len(certs) == 0 { if len(certs) == 0 {
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType,
"%s challenge for %s resulted in no certificates", ch.Type, ch.Value)) "%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] 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 no DNS names present, look for IP address and verify that exactly one exists
if len(leafCert.DNSNames) == 0 { if len(leafCert.DNSNames) == 0 {
@ -262,6 +265,50 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK
return nil 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 // KeyAuthorization creates the ACME key authorization value from a token
// and a jwk. // and a jwk.
func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) { func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) {